Skip to content

Instantly share code, notes, and snippets.

@vishalnarkhede
Created November 3, 2020 10:05
Show Gist options
  • Save vishalnarkhede/55c4955c00c1110511f51fd7b514e286 to your computer and use it in GitHub Desktop.
Save vishalnarkhede/55c4955c00c1110511f51fd7b514e286 to your computer and use it in GitHub Desktop.
// src/screens/MessageSearchScreen.js
import React, {useEffect, useRef, useState} from 'react';
import {View, StyleSheet} from 'react-native';
import {
FlatList,
TextInput,
TouchableOpacity,
ActivityIndicator,
SafeAreaView,
} from 'react-native';
import {
AsyncStore,
ChatClientService,
getChannelDisplayName,
useStreamChatTheme,
} from '../utils';
import {
Message as DefaultMessage,
ThemeProvider,
} from 'stream-chat-react-native';
import {useNavigation, useTheme} from '@react-navigation/native';
import {MessageSlack} from '../components/MessageSlack';
import {SCText} from '../components/SCText';
import {ListItemSeparator} from '../components/ListItemSeparator';
export const MessageSearchScreen = () => {
const {colors, dark} = useTheme();
const navigation = useNavigation();
const chatStyle = useStreamChatTheme();
const inputRef = useRef(null);
const [results, setResults] = useState(null);
const [recentSearches, setRecentSearches] = useState([]);
const [loadingResults, setLoadingResults] = useState(false);
const [searchText, setSearchText] = useState('');
const addToRecentSearches = async q => {
const _recentSearches = [...recentSearches];
_recentSearches.unshift(q);
// Store only max 10 searches
const slicesRecentSearches = _recentSearches.slice(0, 7);
setRecentSearches(slicesRecentSearches);
await AsyncStore.setItem(
'@slack-clone-recent-searches',
slicesRecentSearches,
);
};
const removeFromRecentSearches = async index => {
const _recentSearches = [...recentSearches];
_recentSearches.splice(index, 1);
setRecentSearches(_recentSearches);
await AsyncStore.setItem('@slack-clone-recent-searches', _recentSearches);
};
const search = async q => {
if (!q) {
setLoadingResults(false);
return;
}
const chatClient = ChatClientService.getClient();
try {
const res = await chatClient.search(
{
members: {
$in: [chatClient.user.id],
},
},
q,
{limit: 10, offset: 0},
);
setResults(res.results.map(r => r.message));
} catch (error) {
setResults([]);
}
setLoadingResults(false);
addToRecentSearches(q);
};
const startNewSearch = () => {
setSearchText('');
setResults(null);
setLoadingResults(false);
inputRef.current.focus();
};
useEffect(() => {
const loadRecentSearches = async () => {
const recentSearches = await AsyncStore.getItem(
'@slack-clone-recent-searches',
[],
);
setRecentSearches(recentSearches);
};
loadRecentSearches();
}, []);
return (
<SafeAreaView
style={[
styles.safeAreaView,
{
backgroundColor: colors.background,
},
]}>
<View style={styles.container}>
<View
style={[
styles.headerContainer,
{
backgroundColor: colors.backgroundSecondary,
},
]}>
<TextInput
ref={ref => {
inputRef.current = ref;
}}
returnKeyType="search"
autoFocus
value={searchText}
onChangeText={text => {
setSearchText(text);
setResults(null);
}}
onSubmitEditing={({nativeEvent: {text, eventCount, target}}) => {
setLoadingResults(true);
search(text);
}}
placeholder="Search for message"
placeholderTextColor={colors.text}
style={[
styles.inputBox,
{
backgroundColor: dark ? '#363639' : '#dcdcdc',
borderColor: dark ? '#212527' : '#D3D3D3',
color: colors.text,
},
]}
/>
<TouchableOpacity
style={styles.cancelButton}
onPress={() => {
navigation.goBack();
}}>
<SCText>Cancel</SCText>
</TouchableOpacity>
</View>
{results && results.length > 0 && (
<View
style={[
styles.resultCountContainer,
{
backgroundColor: colors.background,
borderColor: colors.border,
},
]}>
<SCText>{results.length} Results</SCText>
</View>
)}
<View
style={[
styles.recentSearchesContainer,
{
backgroundColor: colors.background,
},
]}>
{!results && !loadingResults && (
<>
<SCText
style={[
styles.recentSearchesTitle,
{
backgroundColor: colors.backgroundSecondary,
},
]}>
Recent searches
</SCText>
<FlatList
keyboardShouldPersistTaps="always"
ItemSeparatorComponent={ListItemSeparator}
data={recentSearches}
renderItem={({item, index}) => {
return (
<TouchableOpacity
onPress={() => {
setSearchText(item);
}}
style={styles.recentSearchItemContainer}>
<SCText style={styles.recentSearchText}>{item}</SCText>
<SCText
onPress={() => {
removeFromRecentSearches(index);
}}>
X
</SCText>
</TouchableOpacity>
);
}}
/>
</>
)}
{loadingResults && (
<View style={styles.loadingIndicatorContainer}>
<ActivityIndicator size="small" color="black" />
</View>
)}
{results && (
<View style={styles.resultsContainer}>
<FlatList
keyboardShouldPersistTaps="always"
contentContainerStyle={{flexGrow: 1}}
ListEmptyComponent={() => {
return (
<View style={styles.listEmptyContainer}>
<SCText>No results for "{searchText}"</SCText>
<TouchableOpacity
onPress={startNewSearch}
style={styles.resetButton}>
<SCText>Start a new search</SCText>
</TouchableOpacity>
</View>
);
}}
data={results}
renderItem={({item}) => {
return (
<TouchableOpacity
onPress={() => {
navigation.navigate('TargettedMessageChannelScreen', {
message: item,
});
}}
style={[
styles.resultItemContainer,
{
backgroundColor: colors.background,
},
]}>
<SCText style={styles.resultChannelTitle}>
{getChannelDisplayName(item.channel, true)}
</SCText>
<ThemeProvider style={chatStyle}>
<DefaultMessage
Message={props => (
<MessageSlack
{...props}
onPress={() => {
navigation.navigate(
'TargettedMessageChannelScreen',
{
message: item,
},
);
}}
/>
)}
message={item}
groupStyles={['single']}
/>
</ThemeProvider>
</TouchableOpacity>
);
}}
/>
</View>
)}
</View>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeAreaView: {
flex: 1,
height: '100%',
},
container: {
flexDirection: 'column',
height: '100%',
},
headerContainer: {
flexDirection: 'row',
width: '100%',
padding: 10,
},
inputBox: {
flex: 1,
margin: 3,
padding: 10,
borderWidth: 0.5,
borderRadius: 10,
},
cancelButton: {justifyContent: 'center', padding: 5},
resultCountContainer: {
padding: 15,
borderBottomWidth: 0.5,
},
recentSearchesContainer: {
marginTop: 10,
marginBottom: 10,
flexGrow: 1,
flexShrink: 1,
},
recentSearchesTitle: {
padding: 5,
fontSize: 13,
},
recentSearchItemContainer: {
padding: 10,
justifyContent: 'space-between',
flexDirection: 'row',
},
recentSearchText: {fontSize: 14},
loadingIndicatorContainer: {
flexGrow: 1,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
},
resultsContainer: {flexGrow: 1, flexShrink: 1},
listEmptyContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
resetButton: {
padding: 15,
paddingTop: 10,
paddingBottom: 10,
marginTop: 10,
borderColor: '#696969',
borderWidth: 0.5,
borderRadius: 5,
},
resultItemContainer: {
padding: 10,
},
resultChannelTitle: {
paddingTop: 10,
paddingBottom: 10,
fontWeight: '700',
color: '#8b8b8b',
},
});
// src/screens/TargettedMessageChannelScreen.js
import {useNavigation, useRoute, useTheme} from '@react-navigation/native';
import React, {useEffect, useState} from 'react';
import {View, SafeAreaView, StyleSheet, Text} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {Chat, Channel, MessageList} from 'stream-chat-react-native';
import {ChannelHeader} from '../components/ChannelHeader';
import {CustomKeyboardCompatibleView} from '../components/CustomKeyboardCompatibleView';
import {DateSeparator} from '../components/DateSeparator';
import {MessageSlack} from '../components/MessageSlack';
import {ChatClientService, useStreamChatTheme} from '../utils';
export const TargettedMessageChannelScreen = () => {
const chatTheme = useStreamChatTheme();
const navigation = useNavigation();
const {
params: {message = null},
} = useRoute();
const {colors} = useTheme();
const chatClient = ChatClientService.getClient();
const [channel, setChannel] = useState(null);
useEffect(() => {
const initChannel = async () => {
if (!message) {
navigation.goBack();
} else {
const _channel = chatClient.channel('messaging', message.channel.id);
const res = await _channel.query({
messages: {limit: 10, id_lte: message.id},
});
// We are tricking Channel component from stream-chat-react-native into believing
// that provided channel is initialized, so that it doesn't call .watch() on channel.
_channel.initialized = true;
setChannel(_channel);
}
};
initChannel();
}, [message]);
if (!channel) {
return null;
}
return (
<SafeAreaView
style={{
backgroundColor: colors.background,
}}>
<View style={styles.channelScreenContainer}>
<ChannelHeader channel={channel} goBack={navigation.goBack} />
<View style={styles.chatContainer}>
<Chat client={chatClient} style={chatTheme}>
<Channel
channel={channel}
KeyboardCompatibleView={CustomKeyboardCompatibleView}>
<MessageList
Message={MessageSlack}
DateSeparator={DateSeparator}
additionalFlatListProps={{
onEndReached: () => null,
}}
/>
</Channel>
</Chat>
</View>
<TouchableOpacity
style={[
styles.recentMessageLink,
{
backgroundColor: colors.primary,
},
]}
onPress={() => {
channel.initialized = false;
navigation.navigate('ChannelScreen', {
channelId: channel.id,
});
}}>
<Text style={styles.recentMessageLinkText}>
Jump to recent message
</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
channelScreenSaveAreaView: {
backgroundColor: '#F7F7F7',
},
channelScreenContainer: {flexDirection: 'column', height: '100%'},
container: {
flex: 1,
backgroundColor: 'white',
},
drawerNavigator: {
backgroundColor: '#3F0E40',
width: 350,
},
chatContainer: {
flexGrow: 1,
flexShrink: 1,
},
recentMessageLink: {
height: 60,
alignSelf: 'center',
width: '100%',
paddingTop: 20,
},
recentMessageLinkText: {
alignSelf: 'center',
color: '#1E90FF',
fontSize: 15,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment