@@ -15,6 +15,8 @@ import {
1515 TextInput ,
1616 useWindowDimensions ,
1717 View ,
18+ NativeSyntheticEvent ,
19+ NativeScrollEvent ,
1820} from 'react-native' ;
1921import { createStyleSheet , useStyles } from 'react-native-unistyles' ;
2022import { Button } from '../Button' ;
@@ -90,6 +92,13 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
9092 const [ connectionError , setConnectionError ] = useState < string | null > ( null ) ;
9193 const [ isConnecting , setIsConnecting ] = useState < boolean > ( false ) ;
9294 const [ showEmotePicker , setShowEmotePicker ] = useState < boolean > ( false ) ;
95+ const [ paused , setPaused ] = useState ( false ) ;
96+ const pausedRef = useRef ( paused ) ;
97+
98+ // Keep pausedRef in sync with paused
99+ useEffect ( ( ) => {
100+ pausedRef . current = paused ;
101+ } , [ paused ] ) ;
93102
94103 const connectToChat = async ( ) => {
95104 if ( isConnecting ) return ;
@@ -155,18 +164,15 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
155164 sender : userstate . username || '' ,
156165 } ;
157166
158- // Append the new message to the existing messages
159- messagesRef . current = [ ...messagesRef . current , newMessage ] ;
160- setMessages ( [ ...messagesRef . current ] ) ;
161-
162- // Scroll to the end of the chat
163- flashListRef . current ?. scrollToEnd ( { animated : false } ) ;
167+ addMessageAndMaybeScroll ( newMessage ) ;
164168 } ) ;
165169
166170 client . on ( 'clearchat' , ( ) => {
167171 messagesRef . current = [ ] ;
168172 setMessages ( [ ] ) ;
169- flashListRef . current ?. scrollToEnd ( { animated : false } ) ;
173+ setTimeout ( ( ) => {
174+ flashListRef . current ?. scrollToEnd ( { animated : false } ) ;
175+ } , 0 ) ;
170176 } ) ;
171177
172178 client . on ( 'disconnected' , reason => {
@@ -187,6 +193,35 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
187193 // eslint-disable-next-line react-hooks/exhaustive-deps
188194 } , [ status ] ) ;
189195
196+ // Helper to check if user is at the bottom
197+ const handleScroll = ( event : NativeSyntheticEvent < NativeScrollEvent > ) => {
198+ const { layoutMeasurement, contentOffset, contentSize } = event . nativeEvent ;
199+ const paddingToBottom = 20 ;
200+ const isAtBottom =
201+ layoutMeasurement . height + contentOffset . y >=
202+ contentSize . height - paddingToBottom ;
203+ setPaused ( ! isAtBottom ) ;
204+ } ;
205+
206+ // Resume chat when user taps overlay
207+ const handleResume = ( ) => {
208+ setPaused ( false ) ;
209+ setTimeout ( ( ) => {
210+ flashListRef . current ?. scrollToEnd ( { animated : true } ) ;
211+ } , 50 ) ;
212+ } ;
213+
214+ // Only auto-scroll if not paused
215+ const addMessageAndMaybeScroll = ( newMessage : ChatMessageProps ) => {
216+ messagesRef . current = [ ...messagesRef . current , newMessage ] ;
217+ setMessages ( [ ...messagesRef . current ] ) ;
218+ if ( ! pausedRef . current ) {
219+ setTimeout ( ( ) => {
220+ flashListRef . current ?. scrollToEnd ( { animated : false } ) ;
221+ } , 0 ) ;
222+ }
223+ } ;
224+
190225 if ( status === 'loading' ) {
191226 return (
192227 < View >
@@ -238,7 +273,17 @@ export const Chat = memo(({ channelName, channelId }: ChatProps) => {
238273 offset : 40 * index ,
239274 index,
240275 } ) }
276+ onScroll = { handleScroll }
277+ scrollEventThrottle = { 16 }
241278 />
279+ { paused && (
280+ < View style = { styles . pausedOverlay } >
281+ < Typography style = { styles . pausedText } > Chat Paused</ Typography >
282+ < Button style = { styles . resumeButton } onPress = { handleResume } >
283+ < Typography style = { styles . resumeButtonText } > Resume</ Typography >
284+ </ Button >
285+ </ View >
286+ ) }
242287 </ View >
243288 < View style = { styles . inputContainer } >
244289 < Button
0 commit comments