this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[🐴] List Adjustments (#3857)

authored by

Hailey and committed by
GitHub
eb55bdf1 c223bcda

+48 -53
+4 -1
src/screens/Messages/Conversation/MessageInput.tsx
··· 13 13 import {useLingui} from '@lingui/react' 14 14 15 15 import {HITSLOP_10} from '#/lib/constants' 16 + import {useHaptics} from 'lib/haptics' 16 17 import {atoms as a, useTheme} from '#/alf' 17 18 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' 18 19 ··· 25 26 }) { 26 27 const {_} = useLingui() 27 28 const t = useTheme() 29 + const playHaptic = useHaptics() 28 30 const [message, setMessage] = React.useState('') 29 31 const [maxHeight, setMaxHeight] = React.useState<number | undefined>() 30 32 const [isInputScrollable, setIsInputScrollable] = React.useState(false) ··· 38 40 return 39 41 } 40 42 onSendMessage(message.trimEnd()) 43 + playHaptic() 41 44 setMessage('') 42 45 setTimeout(() => { 43 46 inputRef.current?.focus() 44 47 }, 100) 45 - }, [message, onSendMessage]) 48 + }, [message, onSendMessage, playHaptic]) 46 49 47 50 const onInputLayout = React.useCallback( 48 51 (e: NativeSyntheticEvent<TextInputContentSizeChangeEventData>) => {
+44 -30
src/screens/Messages/Conversation/MessagesList.tsx
··· 1 1 import React, {useCallback, useRef} from 'react' 2 - import {FlatList, Platform, View} from 'react-native' 3 - import {KeyboardAvoidingView} from 'react-native-keyboard-controller' 2 + import {FlatList, View} from 'react-native' 3 + import { 4 + KeyboardAvoidingView, 5 + useKeyboardHandler, 6 + } from 'react-native-keyboard-controller' 4 7 import {runOnJS, useSharedValue} from 'react-native-reanimated' 5 8 import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/reanimated2/hook/commonTypes' 6 9 import {useSafeAreaInsets} from 'react-native-safe-area-context' ··· 15 18 import {List} from 'view/com/util/List' 16 19 import {MessageInput} from '#/screens/Messages/Conversation/MessageInput' 17 20 import {MessageListError} from '#/screens/Messages/Conversation/MessageListError' 18 - import {useScrollToEndOnFocus} from '#/screens/Messages/Conversation/useScrollToEndOnFocus' 19 21 import {atoms as a, useBreakpoints} from '#/alf' 20 22 import {Button, ButtonText} from '#/components/Button' 21 23 import {MessageItem} from '#/components/dms/MessageItem' ··· 96 98 // onStartReached to fire. 97 99 const contentHeight = useSharedValue(0) 98 100 99 - const [hasInitiallyScrolled, setHasInitiallyScrolled] = React.useState(false) 101 + // We don't want to call `scrollToEnd` again if we are already scolling to the end, because this creates a bit of jank 102 + // Instead, we use `onMomentumScrollEnd` and this value to determine if we need to start scrolling or not. 103 + const isMomentumScrolling = useSharedValue(false) 100 104 101 - // This is only used on native because `Keyboard` can't be imported on web. On web, an input focus will immediately 102 - // trigger scrolling to the bottom. On native however, we need to wait for the keyboard to present before scrolling, 103 - // which is what this hook listens for 104 - useScrollToEndOnFocus(flatListRef) 105 + const [hasInitiallyScrolled, setHasInitiallyScrolled] = React.useState(false) 105 106 106 107 // Every time the content size changes, that means one of two things is happening: 107 108 // 1. New messages are being added from the log or from a message you have sent ··· 126 127 animated: hasInitiallyScrolled, 127 128 offset: height, 128 129 }) 130 + isMomentumScrolling.value = true 129 131 }, 130 - [contentHeight, hasInitiallyScrolled, isAtBottom.value], 132 + [ 133 + contentHeight, 134 + hasInitiallyScrolled, 135 + isAtBottom.value, 136 + isMomentumScrolling, 137 + ], 131 138 ) 132 139 133 140 // The check for `hasInitiallyScrolled` prevents an initial fetch on mount. FlatList triggers `onStartReached` ··· 168 175 [contentHeight.value, hasInitiallyScrolled, isAtBottom], 169 176 ) 170 177 178 + const onMomentumEnd = React.useCallback(() => { 179 + 'worklet' 180 + isMomentumScrolling.value = false 181 + }, [isMomentumScrolling]) 182 + 171 183 const scrollToEnd = React.useCallback(() => { 172 - requestAnimationFrame(() => 173 - flatListRef.current?.scrollToEnd({animated: true}), 174 - ) 175 - }, []) 184 + requestAnimationFrame(() => { 185 + if (isMomentumScrolling.value) return 176 186 177 - const {bottom: bottomInset} = useSafeAreaInsets() 187 + flatListRef.current?.scrollToEnd({animated: true}) 188 + isMomentumScrolling.value = true 189 + }) 190 + }, [isMomentumScrolling]) 191 + 192 + const {bottom: bottomInset, top: topInset} = useSafeAreaInsets() 178 193 const {gtMobile} = useBreakpoints() 179 194 const bottomBarHeight = gtMobile ? 0 : isIOS ? 40 : 60 180 - const keyboardVerticalOffset = useKeyboardVerticalOffset() 195 + 196 + // This is only used inside the useKeyboardHandler because the worklet won't work with a ref directly. 197 + const scrollToEndNow = React.useCallback(() => { 198 + flatListRef.current?.scrollToEnd({animated: false}) 199 + }, []) 200 + 201 + useKeyboardHandler({ 202 + onMove: () => { 203 + 'worklet' 204 + runOnJS(scrollToEndNow)() 205 + }, 206 + }) 181 207 182 208 return ( 183 209 <KeyboardAvoidingView 184 210 style={[a.flex_1, {marginBottom: bottomInset + bottomBarHeight}]} 185 - keyboardVerticalOffset={keyboardVerticalOffset} 211 + keyboardVerticalOffset={isIOS ? topInset : 0} 186 212 behavior="padding" 187 213 contentContainerStyle={a.flex_1}> 188 214 {/* This view keeps the scroll bar and content within the CenterView on web, otherwise the entire window would scroll */} 189 215 {/* @ts-expect-error web only */} 190 216 <View style={[{flex: 1}, isWeb && {'overflow-y': 'scroll'}]}> 191 - {/* Custom scroll provider so we can use the `onScroll` event in our custom List implementation */} 192 - <ScrollProvider onScroll={onScroll}> 217 + {/* Custom scroll provider so that we can use the `onScroll` event in our custom List implementation */} 218 + <ScrollProvider onScroll={onScroll} onMomentumEnd={onMomentumEnd}> 193 219 <List 194 220 ref={flatListRef} 195 221 data={chat.status === ConvoStatus.Ready ? chat.items : undefined} ··· 222 248 </KeyboardAvoidingView> 223 249 ) 224 250 } 225 - 226 - function useKeyboardVerticalOffset() { 227 - const {top: topInset} = useSafeAreaInsets() 228 - 229 - return Platform.select({ 230 - ios: topInset, 231 - // I thought this might be the navigation bar height, but not sure 232 - // 25 is just trial and error 233 - android: 25, 234 - default: 0, 235 - }) 236 - }
-16
src/screens/Messages/Conversation/useScrollToEndOnFocus.ts
··· 1 - import React from 'react' 2 - import {FlatList, Keyboard} from 'react-native' 3 - 4 - export function useScrollToEndOnFocus(flatListRef: React.RefObject<FlatList>) { 5 - React.useEffect(() => { 6 - const listener = Keyboard.addListener('keyboardDidShow', () => { 7 - requestAnimationFrame(() => { 8 - flatListRef.current?.scrollToEnd({animated: true}) 9 - }) 10 - }) 11 - 12 - return () => { 13 - listener.remove() 14 - } 15 - }, [flatListRef]) 16 - }
-6
src/screens/Messages/Conversation/useScrollToEndOnFocus.web.ts
··· 1 - import React from 'react' 2 - import {FlatList} from 'react-native' 3 - 4 - // Stub for web 5 - // eslint-disable-next-line @typescript-eslint/no-unused-vars 6 - export function useScrollToEndOnFocus(flatListRef: React.RefObject<FlatList>) {}