Clean up scrolling
This commit is contained in:
+48
-138
@@ -30,7 +30,7 @@ import {PostThreadComposePrompt} from '#/view/com/post-thread/PostThreadComposeP
|
|||||||
import {PostThreadItem} from '#/view/com/post-thread/PostThreadItem'
|
import {PostThreadItem} from '#/view/com/post-thread/PostThreadItem'
|
||||||
import {PostThreadShowHiddenReplies} from '#/view/com/post-thread/PostThreadShowHiddenReplies'
|
import {PostThreadShowHiddenReplies} from '#/view/com/post-thread/PostThreadShowHiddenReplies'
|
||||||
import {List, type ListMethods} from '#/view/com/util/List'
|
import {List, type ListMethods} from '#/view/com/util/List'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonIcon} from '#/components/Button'
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
|
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
@@ -121,56 +121,6 @@ function useThreadPreferences() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useControlledListScrolling({
|
|
||||||
isResetting,
|
|
||||||
}: {
|
|
||||||
isResetting: boolean
|
|
||||||
}) {
|
|
||||||
const ref = useRef<ListMethods>(null)
|
|
||||||
const layoutHeaderRef = useRef<View | null>(null)
|
|
||||||
const anchorPostRef = useRef<View | null>(null)
|
|
||||||
|
|
||||||
const didAdjustScrollWeb = useRef<boolean>(false)
|
|
||||||
if (didAdjustScrollWeb.current && isResetting) {
|
|
||||||
ref.current?.scrollToOffset({
|
|
||||||
animated: false,
|
|
||||||
offset: 0,
|
|
||||||
})
|
|
||||||
didAdjustScrollWeb.current = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
listRef: ref,
|
|
||||||
headerRef: layoutHeaderRef,
|
|
||||||
anchorRef: anchorPostRef,
|
|
||||||
handleScrollReset() {
|
|
||||||
// only run once
|
|
||||||
// TODO need to handle this any time the query changes
|
|
||||||
if (didAdjustScrollWeb.current) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!isResetting) {
|
|
||||||
// Measure synchronously to avoid a layout jump.
|
|
||||||
const anchorPost = anchorPostRef.current as any as Element
|
|
||||||
const headerNode = layoutHeaderRef.current as any as Element
|
|
||||||
if (anchorPost && headerNode) {
|
|
||||||
// get new scroll position
|
|
||||||
let pageY = anchorPost.getBoundingClientRect().top
|
|
||||||
// subtract header height
|
|
||||||
pageY -= headerNode.getBoundingClientRect().height
|
|
||||||
// don't scroll past 0
|
|
||||||
pageY = Math.max(0, pageY)
|
|
||||||
ref.current?.scrollToOffset({
|
|
||||||
animated: false,
|
|
||||||
offset: pageY,
|
|
||||||
})
|
|
||||||
didAdjustScrollWeb.current = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Inner({uri}: {uri: string | undefined}) {
|
export function Inner({uri}: {uri: string | undefined}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
@@ -192,23 +142,18 @@ export function Inner({uri}: {uri: string | undefined}) {
|
|||||||
Set<HiddenReplyKind>
|
Set<HiddenReplyKind>
|
||||||
>(new Set())
|
>(new Set())
|
||||||
|
|
||||||
const {isFetching, isPlaceholderData, error, data, refetch, insertReplies, ...rest} =
|
const {isFetching, error, data, refetch, insertReplies} = usePostThread({
|
||||||
usePostThread({
|
uri,
|
||||||
uri,
|
enabled: isThreadPreferencesLoaded,
|
||||||
enabled: isThreadPreferencesLoaded,
|
params: {
|
||||||
params: {
|
sort: sortReplies,
|
||||||
sort: sortReplies,
|
view: treeViewEnabled ? 'tree' : 'linear',
|
||||||
view: treeViewEnabled ? 'tree' : 'linear',
|
prioritizeFollows: prioritizeFollowedUsers,
|
||||||
prioritizeFollows: prioritizeFollowedUsers,
|
},
|
||||||
},
|
state: {
|
||||||
state: {
|
shownHiddenReplyKinds,
|
||||||
shownHiddenReplyKinds,
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const ref = useRef<ListMethods>(null)
|
|
||||||
const layoutHeaderRef = useRef<View | null>(null)
|
|
||||||
const anchorPostRef = useRef<View | null>(null)
|
|
||||||
|
|
||||||
const optimisticOnPostReply = (
|
const optimisticOnPostReply = (
|
||||||
_: any,
|
_: any,
|
||||||
@@ -246,10 +191,41 @@ export function Inner({uri}: {uri: string | undefined}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const listRef = useRef<ListMethods>(null)
|
||||||
|
const headerRef = useRef<View | null>(null)
|
||||||
|
const anchorRef = useRef<View | null>(null)
|
||||||
|
/**
|
||||||
|
* WEB ONLY
|
||||||
|
*
|
||||||
|
* Fires any time the content of the list changes. If user switches back to a
|
||||||
|
* sort that was rendered previously, this does NOT fire. Therefore, scroll
|
||||||
|
* is only reset to the anchor on initial render, or fresh data.
|
||||||
|
*
|
||||||
|
* When this fires, the `List` is scrolled all the way to the top, so
|
||||||
|
* measurements taken from `top` correspond to the top of the screen. This
|
||||||
|
* handler scrolls the `List` to the top of the highlighted post, minus any
|
||||||
|
* fixed elements.
|
||||||
|
*/
|
||||||
|
const onContentSizeChangeWebOnly = web(() => {
|
||||||
|
const anchorElement = anchorRef.current as any as Element
|
||||||
|
const headerElement = headerRef.current as any as Element
|
||||||
|
if (anchorElement && headerElement) {
|
||||||
|
// distance from top of the list (screen)
|
||||||
|
const anchorOffsetTop = anchorElement.getBoundingClientRect().top
|
||||||
|
const headerHeight = headerElement.getBoundingClientRect().height
|
||||||
|
// don't scroll past 0
|
||||||
|
const scrollPosition = Math.max(0, anchorOffsetTop - headerHeight)
|
||||||
|
listRef.current?.scrollToOffset({
|
||||||
|
animated: false,
|
||||||
|
offset: scrollPosition,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const renderItem = ({item, index}: {item: Slice; index: number}) => {
|
const renderItem = ({item, index}: {item: Slice; index: number}) => {
|
||||||
if (item.type === 'threadPost') {
|
if (item.type === 'threadPost') {
|
||||||
return (
|
return (
|
||||||
<View ref={item.ui.isAnchor ? anchorPostRef : undefined}>
|
<View ref={item.ui.isAnchor ? anchorRef : undefined}>
|
||||||
<PostThreadItem
|
<PostThreadItem
|
||||||
post={item.value.post}
|
post={item.value.post}
|
||||||
record={item.value.post.record}
|
record={item.value.post.record}
|
||||||
@@ -327,75 +303,9 @@ export function Inner({uri}: {uri: string | undefined}) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const controlScroll = () => {
|
|
||||||
// Measure synchronously to avoid a layout jump.
|
|
||||||
const anchorPost = anchorPostRef.current as any as Element
|
|
||||||
const headerNode = layoutHeaderRef.current as any as Element
|
|
||||||
if (anchorPost && headerNode) {
|
|
||||||
// get new scroll position
|
|
||||||
let pageY = anchorPost.getBoundingClientRect().top
|
|
||||||
console.log('anchor top', pageY)
|
|
||||||
// subtract header height
|
|
||||||
pageY -= headerNode.getBoundingClientRect().height
|
|
||||||
console.log('anchor top minus header', pageY)
|
|
||||||
// don't scroll past 0
|
|
||||||
pageY = Math.max(0, pageY)
|
|
||||||
ref.current?.scrollToOffset({
|
|
||||||
animated: false,
|
|
||||||
offset: pageY,
|
|
||||||
})
|
|
||||||
// didAdjustScrollWeb.current = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is only used on the web to keep the post in view when its parents
|
|
||||||
* load. On native, we rely on `maintainVisibleContentPosition` instead.
|
|
||||||
*
|
|
||||||
* This runs synchronously before layout, meaning by the time the page
|
|
||||||
* paints, we've already measured and adjusted the scroll position of the
|
|
||||||
* list.
|
|
||||||
*/
|
|
||||||
const didAdjustScrollWeb = useRef<boolean>(false)
|
|
||||||
/*
|
|
||||||
if (didAdjustScrollWeb.current && rest.fetchStatus === 'fetching') {
|
|
||||||
ref.current?.scrollToOffset({
|
|
||||||
animated: false,
|
|
||||||
offset: 0,
|
|
||||||
})
|
|
||||||
didAdjustScrollWeb.current = false
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const contentSizeChanged = useRef<boolean>(false)
|
|
||||||
const onContentSizeChangeWeb = () => {
|
|
||||||
if (!contentSizeChanged.current) {
|
|
||||||
console.log('content size changed')
|
|
||||||
controlScroll()
|
|
||||||
contentSizeChanged.current = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This works AFTER the data is in the cache, but not on initial loads
|
|
||||||
*/
|
|
||||||
const lastDataUpdate = useRef<number>(rest.dataUpdatedAt)
|
|
||||||
if (rest.dataUpdatedAt && rest.dataUpdatedAt !== lastDataUpdate.current) {
|
|
||||||
lastDataUpdate.current = rest.dataUpdatedAt
|
|
||||||
|
|
||||||
console.log('data updated, reset scroll')
|
|
||||||
|
|
||||||
ref.current?.scrollToOffset({
|
|
||||||
animated: false,
|
|
||||||
offset: 0,
|
|
||||||
})
|
|
||||||
contentSizeChanged.current = false
|
|
||||||
controlScroll()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Layout.Header.Outer headerRef={layoutHeaderRef}>
|
<Layout.Header.Outer headerRef={headerRef}>
|
||||||
<Layout.Header.BackButton />
|
<Layout.Header.BackButton />
|
||||||
<Layout.Header.Content>
|
<Layout.Header.Content>
|
||||||
<Layout.Header.TitleText>
|
<Layout.Header.TitleText>
|
||||||
@@ -419,11 +329,11 @@ export function Inner({uri}: {uri: string | undefined}) {
|
|||||||
// onMomentumEnd={onMomentumEnd}
|
// onMomentumEnd={onMomentumEnd}
|
||||||
>
|
>
|
||||||
<List
|
<List
|
||||||
ref={ref}
|
ref={listRef}
|
||||||
data={data?.items ?? []}
|
data={data?.items ?? []}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb}
|
onContentSizeChange={onContentSizeChangeWebOnly}
|
||||||
// onStartReached={onStartReached}
|
// onStartReached={onStartReached}
|
||||||
// onEndReached={onEndReached}
|
// onEndReached={onEndReached}
|
||||||
onEndReachedThreshold={2}
|
onEndReachedThreshold={2}
|
||||||
|
|||||||
Reference in New Issue
Block a user