[Chat] Persist left column scroll in splitview (#10513)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {useAnimatedRef} from 'react-native-reanimated'
|
import {useAnimatedRef} from 'react-native-reanimated'
|
||||||
import {type ChatBskyConvoDefs} from '@atproto/api'
|
import {type ChatBskyConvoDefs} from '@atproto/api'
|
||||||
@@ -45,6 +45,7 @@ import {IS_NATIVE} from '#/env'
|
|||||||
import {ChatListItem} from './components/ChatListItem'
|
import {ChatListItem} from './components/ChatListItem'
|
||||||
import {InboxRequests} from './components/InboxRequests'
|
import {InboxRequests} from './components/InboxRequests'
|
||||||
import {useIsWithinSplitView} from './components/splitView/context'
|
import {useIsWithinSplitView} from './components/splitView/context'
|
||||||
|
import {splitViewLeftScroll} from './components/splitView/leftColumnScroll'
|
||||||
|
|
||||||
type ListItem = {
|
type ListItem = {
|
||||||
type: 'CONVERSATION'
|
type: 'CONVERSATION'
|
||||||
@@ -255,12 +256,32 @@ export function ChatList({
|
|||||||
animated: IS_NATIVE,
|
animated: IS_NATIVE,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
})
|
})
|
||||||
|
if (isWithinSplitView) {
|
||||||
|
splitViewLeftScroll.current = 0
|
||||||
|
restoredRef.current = true
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await refetch()
|
await refetch()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Failed to refresh conversations', {message: err})
|
logger.error('Failed to refresh conversations', {message: err})
|
||||||
}
|
}
|
||||||
}, [scrollElRef, refetch])
|
}, [scrollElRef, refetch, isWithinSplitView])
|
||||||
|
|
||||||
|
// Restore the saved scroll offset once the list has rendered enough
|
||||||
|
// content to honor it. Module-level ref survives ChatList re-mounts that
|
||||||
|
// happen on in-splitview navigation (see leftColumnScroll.ts).
|
||||||
|
const restoredRef = useRef(false)
|
||||||
|
const onContentSizeChange = useCallback(
|
||||||
|
(_w: number, h: number) => {
|
||||||
|
if (!isWithinSplitView || restoredRef.current) return
|
||||||
|
const offset = splitViewLeftScroll.current
|
||||||
|
if (offset > 0 && h >= offset) {
|
||||||
|
scrollElRef.current?.scrollToOffset({offset, animated: false})
|
||||||
|
restoredRef.current = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isWithinSplitView, scrollElRef],
|
||||||
|
)
|
||||||
|
|
||||||
const isScreenFocused = useIsFocused()
|
const isScreenFocused = useIsFocused()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -364,6 +385,7 @@ export function ChatList({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onEndReachedThreshold={IS_NATIVE ? 1.5 : 0}
|
onEndReachedThreshold={IS_NATIVE ? 1.5 : 0}
|
||||||
|
onContentSizeChange={onContentSizeChange}
|
||||||
initialNumToRender={initialNumToRender}
|
initialNumToRender={initialNumToRender}
|
||||||
windowSize={11}
|
windowSize={11}
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import {useCallback} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
|
import {type ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes'
|
||||||
import {type ScreenLayoutArgs, useIsFocused} from '@react-navigation/native'
|
import {type ScreenLayoutArgs, useIsFocused} from '@react-navigation/native'
|
||||||
import {type NativeStackNavigationProp} from '@react-navigation/native-stack'
|
import {type NativeStackNavigationProp} from '@react-navigation/native-stack'
|
||||||
|
|
||||||
import {type FlatNavigatorParams} from '#/lib/routes/types'
|
import {type FlatNavigatorParams} from '#/lib/routes/types'
|
||||||
|
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||||
import {type NativeStackNavigationOptionsWithAuth} from '#/view/shell/createNativeStackNavigatorWithAuth'
|
import {type NativeStackNavigationOptionsWithAuth} from '#/view/shell/createNativeStackNavigatorWithAuth'
|
||||||
import {atoms as a, useLayoutBreakpoints, useTheme, web} from '#/alf'
|
import {atoms as a, useLayoutBreakpoints, useTheme, web} from '#/alf'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
@@ -13,6 +16,7 @@ import {useAgeAssurance} from '#/ageAssurance'
|
|||||||
import {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
import {ChatList, Header as ChatListHeader} from '../../ChatList'
|
import {ChatList, Header as ChatListHeader} from '../../ChatList'
|
||||||
import {SplitViewProvider} from './context'
|
import {SplitViewProvider} from './context'
|
||||||
|
import {splitViewLeftScroll} from './leftColumnScroll'
|
||||||
|
|
||||||
const CENTER_COLUMN_WIDTH = 600
|
const CENTER_COLUMN_WIDTH = 600
|
||||||
const LEFT_NAV_FULL_WIDTH = 245
|
const LEFT_NAV_FULL_WIDTH = 245
|
||||||
@@ -48,6 +52,11 @@ function MessagesSplitViewLayout({children, navigation, route}: LayoutProps) {
|
|||||||
const aa = useAgeAssurance()
|
const aa = useAgeAssurance()
|
||||||
const isFocused = useIsFocused()
|
const isFocused = useIsFocused()
|
||||||
|
|
||||||
|
const onLeftColumnScroll = useCallback((e: ReanimatedScrollEvent) => {
|
||||||
|
'worklet'
|
||||||
|
splitViewLeftScroll.current = e.contentOffset.y
|
||||||
|
}, [])
|
||||||
|
|
||||||
if (!IS_WEB || !rightNavVisible || aa.state.access !== aa.Access.Full) {
|
if (!IS_WEB || !rightNavVisible || aa.state.access !== aa.Access.Full) {
|
||||||
return children
|
return children
|
||||||
}
|
}
|
||||||
@@ -106,10 +115,12 @@ function MessagesSplitViewLayout({children, navigation, route}: LayoutProps) {
|
|||||||
{width: containerWidth - centerColumnWidth},
|
{width: containerWidth - centerColumnWidth},
|
||||||
]}>
|
]}>
|
||||||
<ChatListHeader newChatControl={newChatControl} />
|
<ChatListHeader newChatControl={newChatControl} />
|
||||||
<ChatList
|
<ScrollProvider onScroll={onLeftColumnScroll}>
|
||||||
newChatControl={newChatControl}
|
<ChatList
|
||||||
selectedChat={selectedChat}
|
newChatControl={newChatControl}
|
||||||
/>
|
selectedChat={selectedChat}
|
||||||
|
/>
|
||||||
|
</ScrollProvider>
|
||||||
<NewChat onNewChat={onNewChat} control={newChatControl} />
|
<NewChat onNewChat={onNewChat} control={newChatControl} />
|
||||||
</View>
|
</View>
|
||||||
</SplitViewProvider>
|
</SplitViewProvider>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Holds the splitview left column's scroll offset across re-mounts caused
|
||||||
|
// by in-splitview navigation. Reset on full page reload, mirroring the
|
||||||
|
// in-memory semantics of useWebScrollRestoration.
|
||||||
|
export const splitViewLeftScroll = {current: 0}
|
||||||
Reference in New Issue
Block a user