Skip to content

Commit

Permalink
[🐴] Finalize web message screen (#3868)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
haileyok authored May 6, 2024
1 parent bc07019 commit ae7626c
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 35 deletions.
81 changes: 46 additions & 35 deletions src/screens/Messages/Conversation/MessagesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -133,6 +145,7 @@ export function MessagesList() {
contentHeight,
hasInitiallyScrolled,
isAtBottom.value,
isAtTop.value,
isMomentumScrolling,
],
)
Expand Down Expand Up @@ -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
Expand All @@ -172,7 +186,7 @@ export function MessagesList() {
runOnJS(setHasInitiallyScrolled)(true)
}
},
[contentHeight.value, hasInitiallyScrolled, isAtBottom],
[contentHeight.value, hasInitiallyScrolled, isAtBottom, isAtTop],
)

const onMomentumEnd = React.useCallback(() => {
Expand Down Expand Up @@ -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>
)
Expand Down
1 change: 1 addition & 0 deletions src/view/com/util/List.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ function ListImpl<ItemT>(
scrollToTop() {
getScrollableNode()?.scrollTo({top: 0})
},

scrollToOffset({
animated,
offset,
Expand Down

0 comments on commit ae7626c

Please sign in to comment.