@@ -2,7 +2,7 @@ import { FlashList } from '@shopify/flash-list';
22import { useLocalSearchParams } from 'expo-router' ;
33import { delay } from 'lodash-es' ;
44import React , { ReactElement , useCallback , useRef } from 'react' ;
5- import { NativeScrollEvent , NativeSyntheticEvent } from 'react-native' ;
5+ import { GestureResponderEvent , NativeScrollEvent , NativeSyntheticEvent } from 'react-native' ;
66import { useSharedValue , withTiming } from 'react-native-reanimated' ;
77import { AiMessageActions } from '@open-webui-react-native/mobile/chat/features/ai-message-actions' ;
88import { useManageMessageSiblings } from '@open-webui-react-native/mobile/chat/features/use-manage-messages-siblings' ;
@@ -62,7 +62,8 @@ export default function ChatMessagesList({
6262 const isScrollToBottomAvailableTimeout = useRef < NodeJS . Timeout | null | number > ( null ) ; //NOTE: number needs to fix pipeline lint error
6363 const isScrollToBottomVisible = useSharedValue ( 0 ) ;
6464 const previousScrollY = useRef ( 0 ) ;
65- const isNearBottomRef = useRef ( true ) ;
65+ const shouldAutoscrollToBottomRef = useRef ( true ) ;
66+ const previousTouchY = useRef ( 0 ) ;
6667
6768 const { showPreviousSibling, showNextSibling, getSiblingsInfo } = useManageMessageSiblings ( chatId , history ) ;
6869 const { mutate : completeChat } = chatApi . useCompleteChat ( ) ;
@@ -80,7 +81,7 @@ export default function ChatMessagesList({
8081 isScrollToBottomAvailable . current = true ;
8182 } , 500 ) ;
8283
83- if ( isNearBottomRef . current && listRef . current && messages ?. length > 0 ) {
84+ if ( shouldAutoscrollToBottomRef . current ) {
8485 requestAnimationFrame ( ( ) => {
8586 listRef . current ?. scrollToEnd ( { animated : true } ) ;
8687 } ) ;
@@ -114,7 +115,6 @@ export default function ChatMessagesList({
114115 //NOTE: The indent of 100 is needed to display the button not immediately when we start scrolling,
115116 //but when a small distance has been scrolled.
116117 const isNearBottom = scrollY + containerHeight >= contentHeight - 100 ;
117- isNearBottomRef . current = isNearBottom ;
118118
119119 if ( isNearBottom || isScrollingUp ) {
120120 animateScrollToBottom ( 0 ) ;
@@ -127,7 +127,6 @@ export default function ChatMessagesList({
127127 //NOTE: Needs to hide scroll to bottom button to avoid its jumping while scrolling to bottom
128128 animateScrollToBottom ( 0 ) ;
129129 isScrollToBottomAvailable . current = false ;
130- isNearBottomRef . current = true ;
131130
132131 delay ( ( ) => {
133132 isScrollToBottomAvailable . current = true ;
@@ -176,6 +175,23 @@ export default function ChatMessagesList({
176175 onFollowUpPress ( text ) ;
177176 } ;
178177
178+ const handleTouchStart = ( e : GestureResponderEvent ) : void => {
179+ if ( ! isResponseGenerating ) return ;
180+
181+ shouldAutoscrollToBottomRef . current = false ;
182+ previousTouchY . current = e . nativeEvent . pageY ;
183+ } ;
184+
185+ const handleTouchMove = ( e : GestureResponderEvent ) : void => {
186+ if ( ! isResponseGenerating ) return ;
187+
188+ const { pageY } = e . nativeEvent ;
189+ const deltaY = pageY - previousTouchY . current ;
190+
191+ previousTouchY . current = pageY ;
192+ shouldAutoscrollToBottomRef . current = deltaY < 0 ;
193+ } ;
194+
179195 const renderItem = useCallback (
180196 ( { item, index } : { item : Message ; index : number } ) => {
181197 const message = history ?. messages [ item . id ] ;
@@ -253,6 +269,8 @@ export default function ChatMessagesList({
253269 } }
254270 onContentSizeChange = { handleContentSizeChange }
255271 onScroll = { handleScroll }
272+ onTouchStart = { handleTouchStart }
273+ onTouchMove = { handleTouchMove }
256274 scrollEventThrottle = { 16 }
257275 />
258276 < ChatBottomButton isVisible = { isScrollToBottomVisible } onPress = { scrollToBottom } />
0 commit comments