Dejank navigation between thread posts (#2625)
* Dejank parent thread spinner * Fix bottom border/spinner jank * Revert unnecessary changezio/stable
parent
de6b380f4e
commit
ef84f3a25e
|
@ -45,10 +45,9 @@ import {isAndroid, isNative} from '#/platform/detection'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||||
|
|
||||||
const MAINTAIN_VISIBLE_CONTENT_POSITION = {minIndexForVisible: 2}
|
const MAINTAIN_VISIBLE_CONTENT_POSITION = {minIndexForVisible: 1}
|
||||||
|
|
||||||
const TOP_COMPONENT = {_reactKey: '__top_component__'}
|
const TOP_COMPONENT = {_reactKey: '__top_component__'}
|
||||||
const PARENT_SPINNER = {_reactKey: '__parent_spinner__'}
|
|
||||||
const REPLY_PROMPT = {_reactKey: '__reply__'}
|
const REPLY_PROMPT = {_reactKey: '__reply__'}
|
||||||
const DELETED = {_reactKey: '__deleted__'}
|
const DELETED = {_reactKey: '__deleted__'}
|
||||||
const BLOCKED = {_reactKey: '__blocked__'}
|
const BLOCKED = {_reactKey: '__blocked__'}
|
||||||
|
@ -59,11 +58,9 @@ const BOTTOM_COMPONENT = {_reactKey: '__bottom_component__'}
|
||||||
type YieldedItem =
|
type YieldedItem =
|
||||||
| ThreadPost
|
| ThreadPost
|
||||||
| typeof TOP_COMPONENT
|
| typeof TOP_COMPONENT
|
||||||
| typeof PARENT_SPINNER
|
|
||||||
| typeof REPLY_PROMPT
|
| typeof REPLY_PROMPT
|
||||||
| typeof DELETED
|
| typeof DELETED
|
||||||
| typeof BLOCKED
|
| typeof BLOCKED
|
||||||
| typeof PARENT_SPINNER
|
|
||||||
|
|
||||||
export function PostThread({
|
export function PostThread({
|
||||||
uri,
|
uri,
|
||||||
|
@ -152,7 +149,7 @@ function PostThreadLoaded({
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {isTablet, isMobile, isDesktop, isTabletOrMobile} = useWebMediaQueries()
|
const {isMobile, isTabletOrMobile} = useWebMediaQueries()
|
||||||
const ref = useRef<ListMethods>(null)
|
const ref = useRef<ListMethods>(null)
|
||||||
const highlightedPostRef = useRef<View | null>(null)
|
const highlightedPostRef = useRef<View | null>(null)
|
||||||
const needsScrollAdjustment = useRef<boolean>(
|
const needsScrollAdjustment = useRef<boolean>(
|
||||||
|
@ -168,14 +165,12 @@ function PostThreadLoaded({
|
||||||
|
|
||||||
// construct content
|
// construct content
|
||||||
const posts = React.useMemo(() => {
|
const posts = React.useMemo(() => {
|
||||||
let arr = [TOP_COMPONENT].concat(
|
let arr = Array.from(
|
||||||
Array.from(
|
|
||||||
flattenThreadSkeleton(
|
flattenThreadSkeleton(
|
||||||
sortThread(thread, threadViewPrefs),
|
sortThread(thread, threadViewPrefs),
|
||||||
hasSession,
|
hasSession,
|
||||||
treeView,
|
treeView,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
if (arr.length > maxVisible) {
|
if (arr.length > maxVisible) {
|
||||||
arr = arr.slice(0, maxVisible).concat([LOAD_MORE])
|
arr = arr.slice(0, maxVisible).concat([LOAD_MORE])
|
||||||
|
@ -215,15 +210,9 @@ function PostThreadLoaded({
|
||||||
// wait for loading to finish
|
// wait for loading to finish
|
||||||
if (thread.type === 'post' && !!thread.parent) {
|
if (thread.type === 'post' && !!thread.parent) {
|
||||||
function onMeasure(pageY: number) {
|
function onMeasure(pageY: number) {
|
||||||
let spinnerHeight = 0
|
|
||||||
if (isDesktop) {
|
|
||||||
spinnerHeight = 40
|
|
||||||
} else if (isTabletOrMobile) {
|
|
||||||
spinnerHeight = 82
|
|
||||||
}
|
|
||||||
ref.current?.scrollToOffset({
|
ref.current?.scrollToOffset({
|
||||||
animated: false,
|
animated: false,
|
||||||
offset: pageY - spinnerHeight,
|
offset: pageY,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (isNative) {
|
if (isNative) {
|
||||||
|
@ -242,7 +231,7 @@ function PostThreadLoaded({
|
||||||
}
|
}
|
||||||
needsScrollAdjustment.current = false
|
needsScrollAdjustment.current = false
|
||||||
}
|
}
|
||||||
}, [thread, isDesktop, isTabletOrMobile])
|
}, [thread])
|
||||||
|
|
||||||
const onPTR = React.useCallback(async () => {
|
const onPTR = React.useCallback(async () => {
|
||||||
setIsPTRing(true)
|
setIsPTRing(true)
|
||||||
|
@ -257,17 +246,11 @@ function PostThreadLoaded({
|
||||||
const renderItem = React.useCallback(
|
const renderItem = React.useCallback(
|
||||||
({item, index}: {item: YieldedItem; index: number}) => {
|
({item, index}: {item: YieldedItem; index: number}) => {
|
||||||
if (item === TOP_COMPONENT) {
|
if (item === TOP_COMPONENT) {
|
||||||
return isTablet ? (
|
return isTabletOrMobile ? (
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
title={_(msg({message: `Post`, context: 'description'}))}
|
title={_(msg({message: `Post`, context: 'description'}))}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
} else if (item === PARENT_SPINNER) {
|
|
||||||
return (
|
|
||||||
<View style={styles.parentSpinner}>
|
|
||||||
<ActivityIndicator />
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
} else if (item === REPLY_PROMPT && hasSession) {
|
} else if (item === REPLY_PROMPT && hasSession) {
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
@ -318,7 +301,7 @@ function PostThreadLoaded({
|
||||||
// @ts-ignore web-only
|
// @ts-ignore web-only
|
||||||
style={{
|
style={{
|
||||||
// Leave enough space below that the scroll doesn't jump
|
// Leave enough space below that the scroll doesn't jump
|
||||||
height: isNative ? 400 : '100vh',
|
height: isNative ? 600 : '100vh',
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
borderColor: pal.colors.border,
|
borderColor: pal.colors.border,
|
||||||
}}
|
}}
|
||||||
|
@ -326,7 +309,7 @@ function PostThreadLoaded({
|
||||||
)
|
)
|
||||||
} else if (item === CHILD_SPINNER) {
|
} else if (item === CHILD_SPINNER) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.childSpinner}>
|
<View style={[pal.border, styles.childSpinner]}>
|
||||||
<ActivityIndicator />
|
<ActivityIndicator />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -361,7 +344,7 @@ function PostThreadLoaded({
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
hasSession,
|
hasSession,
|
||||||
isTablet,
|
isTabletOrMobile,
|
||||||
isMobile,
|
isMobile,
|
||||||
onPressReply,
|
onPressReply,
|
||||||
pal.border,
|
pal.border,
|
||||||
|
@ -507,12 +490,15 @@ function* flattenThreadSkeleton(
|
||||||
node: ThreadNode,
|
node: ThreadNode,
|
||||||
hasSession: boolean,
|
hasSession: boolean,
|
||||||
treeView: boolean,
|
treeView: boolean,
|
||||||
|
isTraversingReplies: boolean = false,
|
||||||
): Generator<YieldedItem, void> {
|
): Generator<YieldedItem, void> {
|
||||||
if (node.type === 'post') {
|
if (node.type === 'post') {
|
||||||
|
if (!node.ctx.isParentLoading) {
|
||||||
if (node.parent) {
|
if (node.parent) {
|
||||||
yield* flattenThreadSkeleton(node.parent, hasSession, treeView)
|
yield* flattenThreadSkeleton(node.parent, hasSession, treeView, false)
|
||||||
} else if (node.ctx.isParentLoading) {
|
} else if (!isTraversingReplies) {
|
||||||
yield PARENT_SPINNER
|
yield TOP_COMPONENT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!hasSession && node.ctx.depth > 0 && hasPwiOptOut(node)) {
|
if (!hasSession && node.ctx.depth > 0 && hasPwiOptOut(node)) {
|
||||||
return
|
return
|
||||||
|
@ -523,7 +509,7 @@ function* flattenThreadSkeleton(
|
||||||
}
|
}
|
||||||
if (node.replies?.length) {
|
if (node.replies?.length) {
|
||||||
for (const reply of node.replies) {
|
for (const reply of node.replies) {
|
||||||
yield* flattenThreadSkeleton(reply, hasSession, treeView)
|
yield* flattenThreadSkeleton(reply, hasSession, treeView, true)
|
||||||
if (!treeView && !node.ctx.isHighlightedPost) {
|
if (!treeView && !node.ctx.isHighlightedPost) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -567,10 +553,9 @@ const styles = StyleSheet.create({
|
||||||
paddingHorizontal: 18,
|
paddingHorizontal: 18,
|
||||||
paddingVertical: 18,
|
paddingVertical: 18,
|
||||||
},
|
},
|
||||||
parentSpinner: {
|
|
||||||
paddingVertical: 10,
|
|
||||||
},
|
|
||||||
childSpinner: {
|
childSpinner: {
|
||||||
|
borderTopWidth: 1,
|
||||||
|
paddingTop: 40,
|
||||||
paddingBottom: 200,
|
paddingBottom: 200,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {useFocusEffect} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||||
import {makeRecordUri} from 'lib/strings/url-helpers'
|
import {makeRecordUri} from 'lib/strings/url-helpers'
|
||||||
import {ViewHeader} from '../com/util/ViewHeader'
|
|
||||||
import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread'
|
import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread'
|
||||||
import {ComposePrompt} from 'view/com/composer/Prompt'
|
import {ComposePrompt} from 'view/com/composer/Prompt'
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
|
@ -18,8 +17,6 @@ import {clamp} from 'lodash'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {msg} from '@lingui/macro'
|
|
||||||
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
||||||
import {ErrorMessage} from '../com/util/error/ErrorMessage'
|
import {ErrorMessage} from '../com/util/error/ErrorMessage'
|
||||||
import {CenteredView} from '../com/util/Views'
|
import {CenteredView} from '../com/util/Views'
|
||||||
|
@ -30,7 +27,6 @@ import {isWeb} from '#/platform/detection'
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
||||||
export function PostThreadScreen({route}: Props) {
|
export function PostThreadScreen({route}: Props) {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {_} = useLingui()
|
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {fabMinimalShellTransform} = useMinimalShellMode()
|
const {fabMinimalShellTransform} = useMinimalShellMode()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
|
@ -79,9 +75,6 @@ export function PostThreadScreen({route}: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={s.hContentRegion}>
|
<View style={s.hContentRegion}>
|
||||||
{isMobile && (
|
|
||||||
<ViewHeader title={_(msg({message: 'Post', context: 'description'}))} />
|
|
||||||
)}
|
|
||||||
<View style={s.flex1}>
|
<View style={s.flex1}>
|
||||||
{uriError ? (
|
{uriError ? (
|
||||||
<CenteredView>
|
<CenteredView>
|
||||||
|
|
Loading…
Reference in New Issue