Skip to content

Commit cfc3983

Browse files
Merge pull request #854 from GetStream/khushal87-crns-286
feat: scroll to top of the list, when bottom tab is pressed
2 parents 9ad7b91 + 97a834f commit cfc3983

File tree

4 files changed

+198
-121
lines changed

4 files changed

+198
-121
lines changed

examples/SampleApp/src/components/BottomTabs.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ export const BottomTabs: React.FC<BottomTabBarProps> = (props) => {
8080
const isFocused = state.index === index;
8181

8282
const onPress = () => {
83+
navigation.emit({
84+
canPreventDefault: true,
85+
target: route.key,
86+
type: 'tabPress',
87+
});
8388
if (!isFocused) {
8489
navigation.navigate(route.name);
8590
}

examples/SampleApp/src/components/MessageSearch/MessageSearchList.tsx

Lines changed: 124 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -73,114 +73,132 @@ export type MessageSearchListProps = {
7373
refreshList: () => void;
7474
showResultCount?: boolean;
7575
};
76-
export const MessageSearchList: React.FC<MessageSearchListProps> = ({
77-
EmptySearchIndicator,
78-
loading,
79-
loadMore,
80-
messages,
81-
refreshing,
82-
refreshList,
83-
showResultCount = false,
84-
}) => {
85-
const {
86-
theme: {
87-
colors: { black, border, grey, white_snow },
88-
},
89-
} = useTheme();
90-
const navigation = useNavigation();
91-
92-
if (loading && !refreshing && (!messages || messages.length === 0)) {
93-
return (
94-
<View style={styles.indicatorContainer}>
95-
<Spinner />
96-
</View>
97-
);
98-
}
99-
if (!messages && !refreshing) return null;
76+
export const MessageSearchList: React.FC<MessageSearchListProps> = React.forwardRef(
77+
(
78+
props,
79+
scrollRef: React.Ref<FlatList<
80+
MessageResponse<
81+
LocalAttachmentType,
82+
LocalChannelType,
83+
LocalCommandType,
84+
LocalMessageType,
85+
LocalReactionType,
86+
LocalUserType
87+
>
88+
> | null>,
89+
) => {
90+
const {
91+
EmptySearchIndicator,
92+
loading,
93+
loadMore,
94+
messages,
95+
refreshing,
96+
refreshList,
97+
showResultCount = false,
98+
} = props;
99+
const {
100+
theme: {
101+
colors: { black, border, grey, white_snow },
102+
},
103+
} = useTheme();
104+
const navigation = useNavigation();
100105

101-
return (
102-
<>
103-
{messages && showResultCount && (
104-
<View
105-
style={{
106-
backgroundColor: white_snow,
107-
paddingHorizontal: 10,
108-
paddingVertical: 2,
109-
}}
110-
>
111-
<Text style={{ color: grey }}>
112-
{`${messages.length >= MESSAGE_SEARCH_LIMIT ? MESSAGE_SEARCH_LIMIT : messages.length}${
113-
messages.length >= MESSAGE_SEARCH_LIMIT ? '+ ' : ' '
114-
} result${messages.length === 1 ? '' : 's'}`}
115-
</Text>
106+
if (loading && !refreshing && (!messages || messages.length === 0)) {
107+
return (
108+
<View style={styles.indicatorContainer}>
109+
<Spinner />
116110
</View>
117-
)}
118-
<FlatList
119-
contentContainerStyle={styles.contentContainer}
120-
// TODO: Remove the following filter once we have two way scroll functionality on threads.
121-
data={messages ? messages.filter(({ parent_id }) => !parent_id) : []}
122-
keyboardDismissMode='on-drag'
123-
ListEmptyComponent={EmptySearchIndicator}
124-
onEndReached={loadMore}
125-
onRefresh={refreshList}
126-
refreshing={refreshing}
127-
renderItem={({ item }) => (
128-
<TouchableOpacity
129-
onPress={() => {
130-
navigation.navigate('ChannelScreen', {
131-
channelId: item.channel?.id,
132-
messageId: item.id,
133-
});
111+
);
112+
}
113+
if (!messages && !refreshing) return null;
114+
115+
return (
116+
<>
117+
{messages && showResultCount && (
118+
<View
119+
style={{
120+
backgroundColor: white_snow,
121+
paddingHorizontal: 10,
122+
paddingVertical: 2,
134123
}}
135-
style={[styles.itemContainer, { borderBottomColor: border }]}
136-
testID='channel-preview-button'
137124
>
138-
<Avatar image={item.user?.image} name={item.user?.name} size={40} />
139-
<View style={styles.flex}>
140-
<View style={styles.row}>
141-
<Text numberOfLines={1} style={[styles.titleContainer, { color: black }]}>
142-
<Text style={styles.title}>{`${item.user?.name} `}</Text>
143-
{!!item.channel?.name && (
144-
<Text style={styles.detailsText}>
145-
in
146-
<Text style={styles.title}>{` ${item.channel?.name}`}</Text>
147-
</Text>
148-
)}
149-
</Text>
150-
</View>
151-
<View style={styles.row}>
152-
<Text
153-
numberOfLines={1}
154-
style={[
155-
styles.message,
156-
{
157-
color: grey,
158-
},
159-
]}
160-
>
161-
{item.text}
162-
</Text>
163-
<Text
164-
style={[
165-
styles.date,
166-
{
167-
color: grey,
168-
},
169-
]}
170-
>
171-
{dayjs(item.created_at).calendar(undefined, {
172-
lastDay: 'DD/MM', // The day before ( Yesterday at 2:30 AM )
173-
lastWeek: 'DD/MM', // Last week ( Last Monday at 2:30 AM )
174-
sameDay: 'h:mm A', // The same day ( Today at 2:30 AM )
175-
sameElse: 'DD/MM/YYYY', // Everything else ( 17/10/2011 )
176-
})}
177-
</Text>
178-
</View>
179-
</View>
180-
</TouchableOpacity>
125+
<Text style={{ color: grey }}>
126+
{`${
127+
messages.length >= MESSAGE_SEARCH_LIMIT ? MESSAGE_SEARCH_LIMIT : messages.length
128+
}${messages.length >= MESSAGE_SEARCH_LIMIT ? '+ ' : ' '} result${
129+
messages.length === 1 ? '' : 's'
130+
}`}
131+
</Text>
132+
</View>
181133
)}
182-
style={styles.flex}
183-
/>
184-
</>
185-
);
186-
};
134+
<FlatList
135+
contentContainerStyle={styles.contentContainer}
136+
// TODO: Remove the following filter once we have two way scroll functionality on threads.
137+
data={messages ? messages.filter(({ parent_id }) => !parent_id) : []}
138+
keyboardDismissMode='on-drag'
139+
ListEmptyComponent={EmptySearchIndicator}
140+
onEndReached={loadMore}
141+
onRefresh={refreshList}
142+
ref={scrollRef}
143+
refreshing={refreshing}
144+
renderItem={({ item }) => (
145+
<TouchableOpacity
146+
onPress={() => {
147+
navigation.navigate('ChannelScreen', {
148+
channelId: item.channel?.id,
149+
messageId: item.id,
150+
});
151+
}}
152+
style={[styles.itemContainer, { borderBottomColor: border }]}
153+
testID='channel-preview-button'
154+
>
155+
<Avatar image={item.user?.image} name={item.user?.name} size={40} />
156+
<View style={styles.flex}>
157+
<View style={styles.row}>
158+
<Text numberOfLines={1} style={[styles.titleContainer, { color: black }]}>
159+
<Text style={styles.title}>{`${item.user?.name} `}</Text>
160+
{!!item.channel?.name && (
161+
<Text style={styles.detailsText}>
162+
in
163+
<Text style={styles.title}>{` ${item.channel?.name}`}</Text>
164+
</Text>
165+
)}
166+
</Text>
167+
</View>
168+
<View style={styles.row}>
169+
<Text
170+
numberOfLines={1}
171+
style={[
172+
styles.message,
173+
{
174+
color: grey,
175+
},
176+
]}
177+
>
178+
{item.text}
179+
</Text>
180+
<Text
181+
style={[
182+
styles.date,
183+
{
184+
color: grey,
185+
},
186+
]}
187+
>
188+
{dayjs(item.created_at).calendar(undefined, {
189+
lastDay: 'DD/MM', // The day before ( Yesterday at 2:30 AM )
190+
lastWeek: 'DD/MM', // Last week ( Last Monday at 2:30 AM )
191+
sameDay: 'h:mm A', // The same day ( Today at 2:30 AM )
192+
sameElse: 'DD/MM/YYYY', // Everything else ( 17/10/2011 )
193+
})}
194+
</Text>
195+
</View>
196+
</View>
197+
</TouchableOpacity>
198+
)}
199+
style={styles.flex}
200+
/>
201+
</>
202+
);
203+
},
204+
);

examples/SampleApp/src/screens/ChannelListScreen.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React, { useContext, useMemo, useRef, useState } from 'react';
2-
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
3-
import { useNavigation } from '@react-navigation/native';
2+
import { FlatList, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
3+
import { useNavigation, useScrollToTop } from '@react-navigation/native';
44
import { ChannelList, CircleClose, Search, useTheme } from 'stream-chat-react-native';
5-
5+
import { Channel } from 'stream-chat';
66
import { ChannelPreview } from '../components/ChannelPreview';
77
import { ChatScreenHeader } from '../components/ChatScreenHeader';
88
import { MessageSearchList } from '../components/MessageSearch/MessageSearchList';
@@ -76,6 +76,18 @@ export const ChannelListScreen: React.FC = () => {
7676
} = useTheme();
7777

7878
const searchInputRef = useRef<TextInput | null>(null);
79+
const scrollRef = useRef<FlatList<
80+
Channel<
81+
LocalAttachmentType,
82+
LocalChannelType,
83+
LocalCommandType,
84+
LocalMessageType,
85+
LocalEventType,
86+
LocalReactionType,
87+
LocalUserType
88+
>
89+
> | null>(null);
90+
7991
const [searchInputText, setSearchInputText] = useState('');
8092
const [searchQuery, setSearchQuery] = useState('');
8193

@@ -93,6 +105,8 @@ export const ChannelListScreen: React.FC = () => {
93105
[chatClientUserId],
94106
);
95107

108+
useScrollToTop(scrollRef);
109+
96110
const EmptySearchIndicator = () => (
97111
<View style={styles.emptyIndicatorContainer}>
98112
<Search height={112} pathFill={grey_gainsboro} width={112} />
@@ -104,6 +118,22 @@ export const ChannelListScreen: React.FC = () => {
104118

105119
if (!chatClient) return null;
106120

121+
const setScrollRef = (
122+
ref: React.RefObject<FlatList<
123+
Channel<
124+
LocalAttachmentType,
125+
LocalChannelType,
126+
LocalCommandType,
127+
LocalMessageType,
128+
LocalEventType,
129+
LocalReactionType,
130+
LocalUserType
131+
>
132+
> | null>,
133+
) => {
134+
scrollRef.current = ref;
135+
};
136+
107137
return (
108138
<View
109139
style={[
@@ -165,6 +195,7 @@ export const ChannelListScreen: React.FC = () => {
165195
loading={loading}
166196
loadMore={loadMore}
167197
messages={messages}
198+
ref={scrollRef}
168199
refreshing={refreshing}
169200
refreshList={refreshList}
170201
showResultCount
@@ -199,6 +230,7 @@ export const ChannelListScreen: React.FC = () => {
199230
}}
200231
options={options}
201232
Preview={ChannelPreview}
233+
setFlatListRef={setScrollRef}
202234
sort={sort}
203235
/>
204236
</View>

0 commit comments

Comments
 (0)