[🐴] Finalize web message screen (#3868)
* add `onStartReached` to web list * fix `rootMargin` * Add `contain`, handle scroll events * improve types, fix typo * simplify * adjust `scrollToTop` and `scrollToOffset` to support `contain`, add `scrollToEnd` * rename `handleWindowScroll` to `handleScroll` * support basic `maintainVisibleContentPosition` * rename `contain` to `containWeb` * remove unnecessary `flex: 1` * add missing props * add root prop to `Visibility` * add root prop to `Visibility` * revert adding `maintainVisibleContentPosition` * remove unnecessary wrapper * add style * oops * maintain position for web * always apply `flex: 1` to styles when contained * add a contained list to storybook * make `onScroll` a worklet in storybook * revert test code * remove unnecessary `flex: 1`zio/stable
parent
bc07019911
commit
ae7626ce6e
|
@ -94,6 +94,9 @@ export function MessagesList() {
|
|||
// the bottom.
|
||||
const isAtBottom = useSharedValue(true)
|
||||
|
||||
// This will be used on web to assist in determing if we need to maintain the content offset
|
||||
const isAtTop = useSharedValue(true)
|
||||
|
||||
// Used to keep track of the current content height. We'll need this in `onScroll` so we know when to start allowing
|
||||
// onStartReached to fire.
|
||||
const contentHeight = useSharedValue(0)
|
||||
|
@ -116,6 +119,15 @@ export function MessagesList() {
|
|||
// we will not scroll whenever new items get prepended to the top.
|
||||
const onContentSizeChange = useCallback(
|
||||
(_: number, height: number) => {
|
||||
// Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the
|
||||
// previous offset whenever we add new content to the previous offset whenever we add new content to the list.
|
||||
if (isWeb && isAtTop.value && hasInitiallyScrolled) {
|
||||
flatListRef.current?.scrollToOffset({
|
||||
animated: false,
|
||||
offset: height - contentHeight.value,
|
||||
})
|
||||
}
|
||||
|
||||
contentHeight.value = height
|
||||
|
||||
// This number _must_ be the height of the MaybeLoader component
|
||||
|
@ -133,6 +145,7 @@ export function MessagesList() {
|
|||
contentHeight,
|
||||
hasInitiallyScrolled,
|
||||
isAtBottom.value,
|
||||
isAtTop.value,
|
||||
isMomentumScrolling,
|
||||
],
|
||||
)
|
||||
|
@ -164,6 +177,7 @@ export function MessagesList() {
|
|||
// Most apps have a little bit of space the user can scroll past while still automatically scrolling ot the bottom
|
||||
// when a new message is added, hence the 100 pixel offset
|
||||
isAtBottom.value = e.contentSize.height - 100 < bottomOffset
|
||||
isAtTop.value = e.contentOffset.y <= 1
|
||||
|
||||
// This number _must_ be the height of the MaybeLoader component.
|
||||
// We don't check for zero, because the `MaybeLoader` component is always present, even when not visible, which
|
||||
|
@ -172,7 +186,7 @@ export function MessagesList() {
|
|||
runOnJS(setHasInitiallyScrolled)(true)
|
||||
}
|
||||
},
|
||||
[contentHeight.value, hasInitiallyScrolled, isAtBottom],
|
||||
[contentHeight.value, hasInitiallyScrolled, isAtBottom, isAtTop],
|
||||
)
|
||||
|
||||
const onMomentumEnd = React.useCallback(() => {
|
||||
|
@ -211,40 +225,37 @@ export function MessagesList() {
|
|||
keyboardVerticalOffset={isIOS ? topInset : 0}
|
||||
behavior="padding"
|
||||
contentContainerStyle={a.flex_1}>
|
||||
{/* This view keeps the scroll bar and content within the CenterView on web, otherwise the entire window would scroll */}
|
||||
{/* @ts-expect-error web only */}
|
||||
<View style={[a.flex_1, isWeb && {'overflow-y': 'scroll'}]}>
|
||||
{/* Custom scroll provider so that we can use the `onScroll` event in our custom List implementation */}
|
||||
<ScrollProvider onScroll={onScroll} onMomentumEnd={onMomentumEnd}>
|
||||
<List
|
||||
ref={flatListRef}
|
||||
data={chat.status === ConvoStatus.Ready ? chat.items : undefined}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
disableVirtualization={true}
|
||||
initialNumToRender={isWeb ? 50 : 25}
|
||||
maxToRenderPerBatch={isWeb ? 50 : 25}
|
||||
keyboardDismissMode="on-drag"
|
||||
keyboardShouldPersistTaps="handled"
|
||||
maintainVisibleContentPosition={{
|
||||
minIndexForVisible: 1,
|
||||
}}
|
||||
contentContainerStyle={{paddingHorizontal: 10}}
|
||||
removeClippedSubviews={false}
|
||||
onContentSizeChange={onContentSizeChange}
|
||||
onStartReached={onStartReached}
|
||||
onScrollToIndexFailed={onScrollToIndexFailed}
|
||||
scrollEventThrottle={100}
|
||||
ListHeaderComponent={
|
||||
<MaybeLoader
|
||||
isLoading={
|
||||
chat.status === ConvoStatus.Ready && chat.isFetchingHistory
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</ScrollProvider>
|
||||
</View>
|
||||
{/* Custom scroll provider so that we can use the `onScroll` event in our custom List implementation */}
|
||||
<ScrollProvider onScroll={onScroll} onMomentumEnd={onMomentumEnd}>
|
||||
<List
|
||||
ref={flatListRef}
|
||||
data={chat.status === ConvoStatus.Ready ? chat.items : undefined}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
disableVirtualization={true}
|
||||
initialNumToRender={isWeb ? 50 : 25}
|
||||
maxToRenderPerBatch={isWeb ? 50 : 25}
|
||||
keyboardDismissMode="on-drag"
|
||||
keyboardShouldPersistTaps="handled"
|
||||
maintainVisibleContentPosition={{
|
||||
minIndexForVisible: 1,
|
||||
}}
|
||||
containWeb={true}
|
||||
contentContainerStyle={{paddingHorizontal: 10}}
|
||||
removeClippedSubviews={false}
|
||||
onContentSizeChange={onContentSizeChange}
|
||||
onStartReached={onStartReached}
|
||||
onScrollToIndexFailed={onScrollToIndexFailed}
|
||||
scrollEventThrottle={100}
|
||||
ListHeaderComponent={
|
||||
<MaybeLoader
|
||||
isLoading={
|
||||
chat.status === ConvoStatus.Ready && chat.isFetchingHistory
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</ScrollProvider>
|
||||
<MessageInput onSendMessage={onSendMessage} scrollToEnd={scrollToEnd} />
|
||||
</KeyboardAvoidingView>
|
||||
)
|
||||
|
|
|
@ -143,6 +143,7 @@ function ListImpl<ItemT>(
|
|||
scrollToTop() {
|
||||
getScrollableNode()?.scrollTo({top: 0})
|
||||
},
|
||||
|
||||
scrollToOffset({
|
||||
animated,
|
||||
offset,
|
||||
|
|
Loading…
Reference in New Issue