index.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import React, { useState, useEffect, useRef, useCallback } from 'react';
  2. import {
  3. View,
  4. Text,
  5. TouchableOpacity,
  6. StyleSheet,
  7. Image,
  8. Platform,
  9. TouchableHighlight
  10. } from 'react-native';
  11. import { HorizontalTabView, Input, PageWrapper } from 'src/components';
  12. import { NAVIGATION_PAGES } from 'src/types';
  13. import { useFocusEffect, useNavigation } from '@react-navigation/native';
  14. import AddChatIcon from 'assets/icons/messages/chat-plus.svg';
  15. import { API_HOST } from 'src/constants';
  16. import { Colors } from 'src/theme';
  17. import { getFontSize } from 'src/utils';
  18. import SwipeableRow from './Components/SwipeableRow';
  19. import { FlashList } from '@shopify/flash-list';
  20. import ReadIcon from 'assets/icons/messages/check-read.svg';
  21. import UnreadIcon from 'assets/icons/messages/check-unread.svg';
  22. import SearchModal from './Components/SearchUsersModal';
  23. import { SheetManager } from 'react-native-actions-sheet';
  24. import MoreModal from './Components/MoreModal';
  25. import SearchIcon from 'assets/icons/search.svg';
  26. import { storage, StoreType } from 'src/storage';
  27. import { usePostGetChatsListQuery } from '@api/chat';
  28. import moment from 'moment';
  29. import { Chat } from './types';
  30. import PinIcon from 'assets/icons/messages/pin.svg';
  31. type Routes = {
  32. key: 'all' | 'unread' | 'archived';
  33. title: string;
  34. };
  35. const MessagesScreen = () => {
  36. const navigation = useNavigation();
  37. const token = storage.get('token', StoreType.STRING) as string;
  38. const { data: chatsData, refetch } = usePostGetChatsListQuery(token, 0, true);
  39. const [chats, setChats] = useState<Chat[]>([]);
  40. const [index, setIndex] = useState(0);
  41. const [filteredChats, setFilteredChats] = useState<Chat[]>([]);
  42. const [search, setSearch] = useState('');
  43. const openRowRef = useRef<any>(null);
  44. const routes: Routes[] = [
  45. { key: 'all', title: 'All' },
  46. { key: 'unread', title: 'Unread' },
  47. { key: 'archived', title: 'Archived' }
  48. ];
  49. const handleRowOpen = (ref: any) => {
  50. if (openRowRef.current && openRowRef.current !== ref) {
  51. openRowRef.current.close();
  52. }
  53. openRowRef.current = ref;
  54. };
  55. useFocusEffect(() => {
  56. navigation.getParent()?.setOptions({
  57. tabBarStyle: {
  58. display: 'flex',
  59. ...Platform.select({
  60. android: {
  61. height: 58
  62. }
  63. })
  64. }
  65. });
  66. });
  67. useEffect(() => {
  68. if (chatsData && chatsData.conversations) {
  69. setChats(chatsData.conversations);
  70. }
  71. }, [chatsData]);
  72. useFocusEffect(
  73. useCallback(() => {
  74. refetch();
  75. }, [])
  76. );
  77. const filterChatsByTab = () => {
  78. if (index === 1) {
  79. setFilteredChats(
  80. chats
  81. .filter((chat: Chat) => chat.unread_count > 0)
  82. .sort((a: Chat, b: Chat) => b.pin - a.pin || b.pin_order - a.pin_order)
  83. );
  84. } else if (index === 2) {
  85. setFilteredChats([]);
  86. } else {
  87. setFilteredChats(
  88. chats.sort((a: Chat, b: Chat) => b.pin - a.pin || b.pin_order - a.pin_order)
  89. );
  90. }
  91. };
  92. useEffect(() => {
  93. filterChatsByTab();
  94. }, [index, chats]);
  95. const handleDeleteChat = (id: number) => {
  96. const updatedChats = chats.filter((chat: Chat) => chat.uid !== id);
  97. setChats(updatedChats);
  98. };
  99. const searchFilter = (text: string) => {
  100. if (text) {
  101. const newData =
  102. chats?.filter((item: Chat) => {
  103. const itemData = item.short ? item.short.toLowerCase() : ''.toLowerCase();
  104. const textData = text.toLowerCase();
  105. return itemData.indexOf(textData) > -1;
  106. }) ?? [];
  107. setFilteredChats(newData);
  108. setSearch(text);
  109. } else {
  110. filterChatsByTab();
  111. setSearch(text);
  112. }
  113. };
  114. const formatDate = (dateString: Date) => {
  115. const inputDate = moment.utc(dateString).local();
  116. const today = moment().local();
  117. const yesterday = moment().local().subtract(1, 'days');
  118. if (inputDate.isSame(today, 'day')) {
  119. return inputDate.format('HH:mm');
  120. }
  121. if (inputDate.isSame(yesterday, 'day')) {
  122. return 'yesterday';
  123. }
  124. if (!inputDate.isSame(today, 'year')) {
  125. return inputDate.format('DD.MM.YYYY');
  126. }
  127. return inputDate.format('DD.MM');
  128. };
  129. const renderChatItem = ({ item }: { item: Chat }) => {
  130. return (
  131. <SwipeableRow
  132. chat={{ id: item.uid, name: item.name, avatar: item.avatar, pin: item.pin }}
  133. onRowOpen={handleRowOpen}
  134. refetch={refetch}
  135. >
  136. <TouchableHighlight
  137. activeOpacity={0.8}
  138. onPress={() =>
  139. navigation.navigate(
  140. ...([
  141. NAVIGATION_PAGES.CHAT,
  142. {
  143. id: item.uid,
  144. name: item.name,
  145. avatar: item.avatar
  146. }
  147. ] as never)
  148. )
  149. }
  150. underlayColor={Colors.FILL_LIGHT}
  151. >
  152. <View style={styles.chatItem}>
  153. <Image source={{ uri: API_HOST + item.avatar }} style={styles.avatar} />
  154. <View style={{ flex: 1, gap: 6 }}>
  155. <View style={[styles.rowContainer, { alignItems: 'center' }]}>
  156. <Text style={styles.chatName}>{item.name}</Text>
  157. <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
  158. {item.pin === 1 ? <PinIcon height={12} fill={Colors.DARK_BLUE} /> : null}
  159. {item.sent_by !== item.uid && item.status === 3 ? (
  160. <ReadIcon fill={Colors.DARK_BLUE} />
  161. ) : item.sent_by !== item.uid && (item.status === 2 || item.status === 1) ? (
  162. <UnreadIcon fill={Colors.LIGHT_GRAY} />
  163. ) : null}
  164. <Text style={styles.chatTime}>{formatDate(item.updated)}</Text>
  165. </View>
  166. </View>
  167. <View style={[styles.rowContainer, { flex: 1, gap: 6 }]}>
  168. <Text numberOfLines={2} style={styles.chatMessage}>
  169. {item.short}
  170. </Text>
  171. {item.unread_count > 0 ? (
  172. <View style={styles.unreadBadge}>
  173. <Text style={styles.unreadText}>
  174. {item.unread_count > 99 ? '99+' : item.unread_count}
  175. </Text>
  176. </View>
  177. ) : null}
  178. </View>
  179. </View>
  180. </View>
  181. </TouchableHighlight>
  182. </SwipeableRow>
  183. );
  184. };
  185. return (
  186. <PageWrapper style={{ flex: 1, marginLeft: 0, marginRight: 0, gap: 12 }}>
  187. <View style={styles.header}>
  188. <View style={{ width: 30 }} />
  189. <Text style={styles.title}>Messages</Text>
  190. <TouchableOpacity
  191. onPress={() => SheetManager.show('search-modal')}
  192. style={{ width: 30, alignItems: 'flex-end' }}
  193. >
  194. <AddChatIcon />
  195. </TouchableOpacity>
  196. </View>
  197. <View style={[{ paddingHorizontal: '4%' }]}>
  198. <Input
  199. inputMode={'search'}
  200. placeholder={'Search'}
  201. onChange={(text) => searchFilter(text)}
  202. value={search}
  203. icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
  204. height={38}
  205. />
  206. </View>
  207. <HorizontalTabView
  208. index={index}
  209. setIndex={setIndex}
  210. routes={routes}
  211. tabBarStyle={{ paddingHorizontal: '4%' }}
  212. renderScene={({ route }: { route: any }) => (
  213. <FlashList
  214. viewabilityConfig={{
  215. waitForInteraction: true,
  216. itemVisiblePercentThreshold: 50,
  217. minimumViewTime: 1000
  218. }}
  219. data={filteredChats}
  220. renderItem={renderChatItem}
  221. keyExtractor={(item, index) => `${item.uid}-${index}`}
  222. estimatedItemSize={78}
  223. removeClippedSubviews={true}
  224. />
  225. )}
  226. />
  227. <SearchModal />
  228. <MoreModal />
  229. </PageWrapper>
  230. );
  231. };
  232. const styles = StyleSheet.create({
  233. title: { color: Colors.DARK_BLUE, fontFamily: 'redhat-700', fontSize: getFontSize(14) },
  234. header: {
  235. alignItems: 'center',
  236. paddingVertical: 16,
  237. flexDirection: 'row',
  238. justifyContent: 'space-between',
  239. marginLeft: '5%',
  240. marginRight: '5%'
  241. },
  242. chatItem: {
  243. flexDirection: 'row',
  244. paddingVertical: 12,
  245. backgroundColor: Colors.WHITE,
  246. borderBottomWidth: 1,
  247. borderBottomColor: Colors.FILL_LIGHT,
  248. gap: 8,
  249. paddingHorizontal: '4%'
  250. },
  251. avatar: {
  252. width: 54,
  253. height: 54,
  254. borderRadius: 27,
  255. borderWidth: 1,
  256. borderColor: Colors.FILL_LIGHT
  257. },
  258. rowContainer: {
  259. flexDirection: 'row',
  260. alignItems: 'center',
  261. justifyContent: 'space-between'
  262. },
  263. chatName: {
  264. flex: 1,
  265. fontFamily: 'montserrat-700',
  266. fontSize: getFontSize(14),
  267. color: Colors.DARK_BLUE
  268. },
  269. chatMessage: {
  270. flex: 1,
  271. fontSize: getFontSize(12),
  272. fontWeight: '600',
  273. color: Colors.DARK_BLUE,
  274. height: '100%'
  275. },
  276. chatTimeContainer: {
  277. justifyContent: 'center',
  278. alignItems: 'flex-end'
  279. },
  280. chatTime: {
  281. fontSize: getFontSize(12),
  282. fontWeight: '600',
  283. color: Colors.LIGHT_GRAY
  284. },
  285. unreadBadge: {
  286. backgroundColor: Colors.ORANGE,
  287. borderRadius: 9,
  288. paddingHorizontal: 6,
  289. alignItems: 'center',
  290. justifyContent: 'center',
  291. height: 18,
  292. minWidth: 18
  293. },
  294. unreadText: {
  295. color: Colors.WHITE,
  296. fontSize: getFontSize(11),
  297. fontFamily: 'montserrat-700'
  298. },
  299. swipeActions: {
  300. flexDirection: 'row',
  301. alignItems: 'center'
  302. },
  303. swipeButton: {
  304. paddingHorizontal: 20
  305. }
  306. });
  307. export default MessagesScreen;