@droppii-org/chat-mobile 0.2.57 → 0.2.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/lib/module/assets/images/index.js +2 -1
  2. package/lib/module/assets/images/index.js.map +1 -1
  3. package/lib/module/assets/images/search_empty.png +0 -0
  4. package/lib/module/components/Avatar/Avatar.utils.js +5 -3
  5. package/lib/module/components/Avatar/Avatar.utils.js.map +1 -1
  6. package/lib/module/components/Avatar/SingleAvatar.js +2 -0
  7. package/lib/module/components/Avatar/SingleAvatar.js.map +1 -1
  8. package/lib/module/components/ThreadCard/AvatarSection.js +8 -4
  9. package/lib/module/components/ThreadCard/AvatarSection.js.map +1 -1
  10. package/lib/module/config/api-endpoints.js +2 -1
  11. package/lib/module/config/api-endpoints.js.map +1 -1
  12. package/lib/module/constants/index.js +4 -0
  13. package/lib/module/constants/index.js.map +1 -0
  14. package/lib/module/hooks/message/useSearchMessage.js +58 -0
  15. package/lib/module/hooks/message/useSearchMessage.js.map +1 -0
  16. package/lib/module/hooks/query-keys.js +4 -0
  17. package/lib/module/hooks/query-keys.js.map +1 -1
  18. package/lib/module/index.js +1 -0
  19. package/lib/module/index.js.map +1 -1
  20. package/lib/module/screens/ChatInfo/ChatInfoActions.js +1 -1
  21. package/lib/module/screens/ChatInfo/ChatInfoActions.js.map +1 -1
  22. package/lib/module/screens/ChatInfo/ChatInfoMediaRow.js +1 -1
  23. package/lib/module/screens/ChatInfo/ChatInfoMediaRow.js.map +1 -1
  24. package/lib/module/screens/SearchConversation/SearchAllResult.js +123 -0
  25. package/lib/module/screens/SearchConversation/SearchAllResult.js.map +1 -0
  26. package/lib/module/screens/SearchConversation/SearchEmpty.js +32 -0
  27. package/lib/module/screens/SearchConversation/SearchEmpty.js.map +1 -0
  28. package/lib/module/screens/SearchConversation/SearchMessagesResult.js +71 -0
  29. package/lib/module/screens/SearchConversation/SearchMessagesResult.js.map +1 -0
  30. package/lib/module/screens/SearchConversation/SearchResultItem.js +102 -0
  31. package/lib/module/screens/SearchConversation/SearchResultItem.js.map +1 -0
  32. package/lib/module/screens/SearchConversation/TabResult.js +118 -0
  33. package/lib/module/screens/SearchConversation/TabResult.js.map +1 -0
  34. package/lib/module/screens/SearchConversation/index.js +57 -0
  35. package/lib/module/screens/SearchConversation/index.js.map +1 -0
  36. package/lib/module/screens/SearchConversation/types.js +9 -0
  37. package/lib/module/screens/SearchConversation/types.js.map +1 -0
  38. package/lib/module/screens/chat-detail/ChatDetail.js +3 -1
  39. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  40. package/lib/module/services/endpoints.js +2 -1
  41. package/lib/module/services/endpoints.js.map +1 -1
  42. package/lib/module/translation/resources/i18n.js +7 -1
  43. package/lib/module/translation/resources/i18n.js.map +1 -1
  44. package/lib/module/types/chat.js.map +1 -1
  45. package/lib/module/utils/messageUtils.js +5 -5
  46. package/lib/module/utils/messageUtils.js.map +1 -1
  47. package/lib/typescript/src/assets/images/index.d.ts +1 -0
  48. package/lib/typescript/src/assets/images/index.d.ts.map +1 -1
  49. package/lib/typescript/src/components/Avatar/Avatar.types.d.ts +1 -1
  50. package/lib/typescript/src/components/Avatar/Avatar.types.d.ts.map +1 -1
  51. package/lib/typescript/src/components/Avatar/Avatar.utils.d.ts +2 -0
  52. package/lib/typescript/src/components/Avatar/Avatar.utils.d.ts.map +1 -1
  53. package/lib/typescript/src/components/Avatar/SingleAvatar.d.ts.map +1 -1
  54. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +2 -1
  55. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
  56. package/lib/typescript/src/config/api-endpoints.d.ts +1 -0
  57. package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
  58. package/lib/typescript/src/constants/index.d.ts +2 -0
  59. package/lib/typescript/src/constants/index.d.ts.map +1 -0
  60. package/lib/typescript/src/hooks/message/useSearchMessage.d.ts +221 -0
  61. package/lib/typescript/src/hooks/message/useSearchMessage.d.ts.map +1 -0
  62. package/lib/typescript/src/hooks/query-keys.d.ts +4 -0
  63. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  64. package/lib/typescript/src/index.d.ts +1 -0
  65. package/lib/typescript/src/index.d.ts.map +1 -1
  66. package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts +10 -0
  67. package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts.map +1 -0
  68. package/lib/typescript/src/screens/SearchConversation/SearchEmpty.d.ts +2 -0
  69. package/lib/typescript/src/screens/SearchConversation/SearchEmpty.d.ts.map +1 -0
  70. package/lib/typescript/src/screens/SearchConversation/SearchMessagesResult.d.ts +8 -0
  71. package/lib/typescript/src/screens/SearchConversation/SearchMessagesResult.d.ts.map +1 -0
  72. package/lib/typescript/src/screens/SearchConversation/SearchResultItem.d.ts +10 -0
  73. package/lib/typescript/src/screens/SearchConversation/SearchResultItem.d.ts.map +1 -0
  74. package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts +8 -0
  75. package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts.map +1 -0
  76. package/lib/typescript/src/screens/SearchConversation/index.d.ts +7 -0
  77. package/lib/typescript/src/screens/SearchConversation/index.d.ts.map +1 -0
  78. package/lib/typescript/src/screens/SearchConversation/types.d.ts +6 -0
  79. package/lib/typescript/src/screens/SearchConversation/types.d.ts.map +1 -0
  80. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
  81. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  82. package/lib/typescript/src/screens/chat-detail/types.d.ts +1 -0
  83. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  84. package/lib/typescript/src/services/endpoints.d.ts +1 -0
  85. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  86. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  87. package/lib/typescript/src/types/chat.d.ts +13 -1
  88. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  89. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  90. package/package.json +1 -1
  91. package/src/assets/images/index.ts +1 -0
  92. package/src/assets/images/search_empty.png +0 -0
  93. package/src/components/Avatar/Avatar.types.ts +1 -1
  94. package/src/components/Avatar/Avatar.utils.ts +5 -3
  95. package/src/components/Avatar/SingleAvatar.tsx +2 -0
  96. package/src/components/ThreadCard/AvatarSection.tsx +5 -2
  97. package/src/config/api-endpoints.ts +2 -0
  98. package/src/constants/index.ts +1 -0
  99. package/src/hooks/message/useSearchMessage.ts +78 -0
  100. package/src/hooks/query-keys.ts +6 -0
  101. package/src/index.tsx +1 -0
  102. package/src/screens/ChatInfo/ChatInfoActions.tsx +1 -1
  103. package/src/screens/ChatInfo/ChatInfoMediaRow.tsx +1 -1
  104. package/src/screens/SearchConversation/SearchAllResult.tsx +154 -0
  105. package/src/screens/SearchConversation/SearchEmpty.tsx +30 -0
  106. package/src/screens/SearchConversation/SearchMessagesResult.tsx +74 -0
  107. package/src/screens/SearchConversation/SearchResultItem.tsx +107 -0
  108. package/src/screens/SearchConversation/TabResult.tsx +144 -0
  109. package/src/screens/SearchConversation/index.tsx +61 -0
  110. package/src/screens/SearchConversation/types.tsx +5 -0
  111. package/src/screens/chat-detail/ChatDetail.tsx +2 -1
  112. package/src/screens/chat-detail/types.ts +1 -0
  113. package/src/services/endpoints.ts +1 -0
  114. package/src/translation/resources/i18n.ts +7 -0
  115. package/src/types/chat.ts +15 -0
  116. package/src/utils/messageUtils.ts +13 -6
@@ -0,0 +1 @@
1
+ export const PAGE_SIZE = 20;
@@ -0,0 +1,78 @@
1
+ import { useInfiniteQuery } from '@tanstack/react-query';
2
+ import { messageQueryKeys } from '../query-keys';
3
+ import { apiInstance } from '../../services/apis';
4
+ import { ENDPOINTS } from '../../services/endpoints';
5
+ import { MessageType } from '@droppii/openim-rn-client-sdk';
6
+ import type {
7
+ DChatApplicationType,
8
+ ISearchMessageItem,
9
+ ISearchMessagesRequest,
10
+ } from '../../types/chat';
11
+ import type { BaseResponse } from '../../types/auth';
12
+ import { PAGE_SIZE } from '../../constants';
13
+ import { useMemo } from 'react';
14
+
15
+ interface IUseSearchMessageProps {
16
+ query: string;
17
+ conversationId?: string;
18
+ applicationType: DChatApplicationType;
19
+ pageSize?: number;
20
+ }
21
+ export const useSearchMessage = ({
22
+ query,
23
+ conversationId,
24
+ applicationType,
25
+ pageSize = PAGE_SIZE,
26
+ }: IUseSearchMessageProps) => {
27
+ const { data, ...rest } = useInfiniteQuery({
28
+ queryKey: messageQueryKeys.search(query, conversationId),
29
+ queryFn: async ({ pageParam = 1 }) => {
30
+ const params: ISearchMessagesRequest = {
31
+ contentTypes: [
32
+ MessageType.TextMessage,
33
+ MessageType.UrlTextMessage,
34
+ MessageType.QuoteMessage,
35
+ MessageType.AtTextMessage,
36
+ MessageType.CustomMessage,
37
+ ],
38
+ page: pageParam,
39
+ pageSize: pageSize,
40
+ applicationType,
41
+ searchTerm: query,
42
+ };
43
+ const res = await apiInstance?.post<BaseResponse<ISearchMessageItem[]>>(
44
+ ENDPOINTS.chatService.searchMessages(),
45
+ params
46
+ );
47
+ return res?.data;
48
+ },
49
+ getNextPageParam: (lastPage) => {
50
+ const currentPage = lastPage?.pageable?.pageNumber || 1;
51
+ const dataLength = lastPage?.data?.length || 0;
52
+ return dataLength < pageSize ? undefined : currentPage + 1;
53
+ },
54
+ initialPageParam: 1,
55
+ enabled: !!query && !!applicationType,
56
+ });
57
+
58
+ const { dataFlatten } = useMemo(() => {
59
+ if (!data)
60
+ return {
61
+ dataFlatten: [],
62
+ };
63
+
64
+ const allItems =
65
+ data?.pages
66
+ ?.flatMap?.((page) => page?.data)
67
+ ?.filter((item) => item !== undefined) || [];
68
+
69
+ return {
70
+ dataFlatten: allItems,
71
+ };
72
+ }, [data]);
73
+
74
+ return {
75
+ dataFlatten,
76
+ ...rest,
77
+ };
78
+ };
@@ -54,3 +54,9 @@ export const resourceCenterQueryKeys = {
54
54
  byType: (recvID: string, contentType: number) =>
55
55
  [...resourceCenterQueryKeys.all, recvID, contentType] as const,
56
56
  };
57
+
58
+ export const messageQueryKeys = {
59
+ all: ['messages'] as const,
60
+ search: (query: string, conversationId?: string) =>
61
+ [...messageQueryKeys.all, 'search', query, conversationId] as const,
62
+ };
package/src/index.tsx CHANGED
@@ -30,6 +30,7 @@ export { Inbox } from './screens/inbox';
30
30
  export { default as MediaViewerScreen } from './screens/MediaView';
31
31
  export { default as ResourceCenterScreen } from './screens/ResourceCenter';
32
32
  export type { ResourceCenterScreenProps } from './screens/ResourceCenter/types';
33
+ export { default as SearchConversation } from './screens/SearchConversation';
33
34
  export * from './screens/chat-detail';
34
35
  export * from './screens/ChatInfo';
35
36
 
@@ -58,7 +58,7 @@ const ChatInfoActions = memo(
58
58
  if (isTargetActive) {
59
59
  items.push(
60
60
  {
61
- leftNode: { icon: { vectorName: 'user-two-o' } },
61
+ leftNode: { icon: { vectorName: 'user-three-o' } },
62
62
  onPress: onPressCreateGroup,
63
63
  paddingH: '0.75rem',
64
64
  paddingV: '0.75rem',
@@ -20,7 +20,7 @@ const MEDIA_TABS: Array<{
20
20
  },
21
21
  {
22
22
  tab: DThreadResourceContentType.FILE,
23
- iconName: 'paperclip-o',
23
+ iconName: 'folder-o',
24
24
  labelKey: 'chat:info_media_file',
25
25
  },
26
26
  {
@@ -0,0 +1,154 @@
1
+ import {
2
+ KButton,
3
+ KColors,
4
+ KContainer,
5
+ KDivider,
6
+ KImage,
7
+ KLabel,
8
+ KSpacingValue,
9
+ } from '@droppii/libs';
10
+ import { memo, useCallback, useMemo } from 'react';
11
+ import { useChatContext } from '../../context';
12
+ import { useSearchMessage } from '../../hooks/message/useSearchMessage';
13
+ import { SearchConversationTabResultType } from './types';
14
+ import type { ISearchMessageItem } from '../../types/chat';
15
+ import { trans } from '../../translation';
16
+ import { FlatList, StyleSheet } from 'react-native';
17
+ import { SearchEmpty } from './SearchEmpty';
18
+ import SearchResultItem from './SearchResultItem';
19
+
20
+ interface SearchAllResultProps {
21
+ searchTerm: string;
22
+ handleChangeTab: (key: SearchConversationTabResultType) => void;
23
+ onPressItem?: (item: ISearchMessageItem) => void;
24
+ }
25
+
26
+ interface ISearchAllResultItem {
27
+ type: SearchConversationTabResultType;
28
+ data: ISearchMessageItem[];
29
+ title: string;
30
+ }
31
+
32
+ const PAGE_SIZE = 5;
33
+
34
+ const SearchAllResult = memo(
35
+ ({ searchTerm, handleChangeTab, onPressItem }: SearchAllResultProps) => {
36
+ const { applicationType } = useChatContext();
37
+ const { dataFlatten: messagesData } = useSearchMessage({
38
+ query: searchTerm,
39
+ applicationType,
40
+ pageSize: PAGE_SIZE,
41
+ });
42
+
43
+ const data: ISearchAllResultItem[] = useMemo(() => {
44
+ const mData = [
45
+ {
46
+ type: SearchConversationTabResultType.Conversations,
47
+ data: [],
48
+ title: trans('contacts'),
49
+ },
50
+ {
51
+ type: SearchConversationTabResultType.Messages,
52
+ data: messagesData,
53
+ title: trans('messages'),
54
+ },
55
+ ]?.filter((item) => item.data.length > 0);
56
+ return mData;
57
+ }, [messagesData]);
58
+
59
+ const onChangeTab = useCallback(
60
+ (key: SearchConversationTabResultType) => {
61
+ handleChangeTab(key);
62
+ },
63
+ [handleChangeTab]
64
+ );
65
+
66
+ const renderSessionItem = useCallback(
67
+ ({ item }: { item: ISearchAllResultItem }) => {
68
+ return (
69
+ <KContainer.View style={styles.itemContainer}>
70
+ <KContainer.Touchable
71
+ style={styles.header}
72
+ onPress={() => onChangeTab(item.type)}
73
+ >
74
+ <KLabel.Text typo="TextMdBold" flex>
75
+ {item.title}
76
+ <KLabel.Text
77
+ typo="TextNmMedium"
78
+ color={KColors.gray.light}
79
+ >{` (${item.data.length})`}</KLabel.Text>
80
+ </KLabel.Text>
81
+ <KImage.VectorIcons name="angle-right-o" size={20} />
82
+ </KContainer.Touchable>
83
+ {item?.data?.map((childItem) => {
84
+ return (
85
+ <KContainer.View
86
+ style={styles.childItemContainer}
87
+ key={`${item.type}-${childItem?.conversation?.conversationID}-${childItem?.chatLog?.clientMsgID}`}
88
+ >
89
+ <SearchResultItem
90
+ item={childItem}
91
+ tabResultType={item.type}
92
+ onPressItem={onPressItem}
93
+ />
94
+ </KContainer.View>
95
+ );
96
+ })}
97
+ <KButton.Link
98
+ label={trans('view_all')}
99
+ icon={{
100
+ vectorName: 'angle-right-o',
101
+ }}
102
+ iconAlignment="right"
103
+ onPress={() => onChangeTab(item.type)}
104
+ size="sm"
105
+ enhanceStyle={styles.viewAllButton}
106
+ />
107
+ </KContainer.View>
108
+ );
109
+ },
110
+ [onChangeTab, onPressItem]
111
+ );
112
+
113
+ const keyExtractor = useCallback((item: ISearchAllResultItem) => {
114
+ return item.type;
115
+ }, []);
116
+
117
+ const ItemSeparatorComponent = useCallback(() => {
118
+ return <KDivider size={'xs'} background={KColors.pageBackground} />;
119
+ }, []);
120
+
121
+ return (
122
+ <FlatList
123
+ data={data}
124
+ renderItem={renderSessionItem}
125
+ keyExtractor={keyExtractor}
126
+ ListEmptyComponent={<SearchEmpty />}
127
+ ItemSeparatorComponent={ItemSeparatorComponent}
128
+ />
129
+ );
130
+ }
131
+ );
132
+
133
+ export default SearchAllResult;
134
+
135
+ const styles = StyleSheet.create({
136
+ itemContainer: {
137
+ paddingHorizontal: KSpacingValue['0.75rem'],
138
+ },
139
+ header: {
140
+ flexDirection: 'row',
141
+ paddingVertical: KSpacingValue['0.5rem'],
142
+ gap: KSpacingValue['1rem'],
143
+ alignItems: 'center',
144
+ borderBottomWidth: 1,
145
+ borderBottomColor: KColors.border.light,
146
+ },
147
+ childItemContainer: {
148
+ borderBottomWidth: 1,
149
+ borderBottomColor: KColors.border.light,
150
+ },
151
+ viewAllButton: {
152
+ height: 40,
153
+ },
154
+ });
@@ -0,0 +1,30 @@
1
+ import { KContainer, KImage, KLabel, KSpacingValue } from '@droppii/libs';
2
+ import { StyleSheet } from 'react-native';
3
+ import Images from '../../assets/images';
4
+ import { trans } from '../../translation';
5
+
6
+ export const SearchEmpty = () => {
7
+ return (
8
+ <KContainer.View style={styles.container}>
9
+ <KImage.Base
10
+ uri={Images.SEARCH_EMPTY}
11
+ width={100}
12
+ height={100}
13
+ resizeMode="contain"
14
+ />
15
+ <KLabel.Text typo="TextMdNormal" textAlign="center">
16
+ {trans('search:empty_description')}
17
+ </KLabel.Text>
18
+ </KContainer.View>
19
+ );
20
+ };
21
+
22
+ const styles = StyleSheet.create({
23
+ container: {
24
+ paddingHorizontal: KSpacingValue['2rem'],
25
+ paddingVertical: 42,
26
+ justifyContent: 'center',
27
+ alignItems: 'center',
28
+ gap: KSpacingValue['0.75rem'],
29
+ },
30
+ });
@@ -0,0 +1,74 @@
1
+ import { KContainer, KDivider, KSpacingValue } from '@droppii/libs';
2
+ import { memo, useCallback } from 'react';
3
+ import { useSearchMessage } from '../../hooks/message/useSearchMessage';
4
+ import { useChatContext } from '../../context';
5
+ import { FlatList, StyleSheet } from 'react-native';
6
+ import type { ISearchMessageItem } from '../../types/chat';
7
+ import SearchResultItem from './SearchResultItem';
8
+ import { SearchConversationTabResultType } from './types';
9
+ import { SearchEmpty } from './SearchEmpty';
10
+
11
+ interface ISearchMessagesResultProps {
12
+ searchTerm: string;
13
+ onPressItem?: (item: ISearchMessageItem) => void;
14
+ }
15
+ const SearchMessagesResult = memo(
16
+ ({ searchTerm, onPressItem }: ISearchMessagesResultProps) => {
17
+ const { applicationType } = useChatContext();
18
+ const { dataFlatten, hasNextPage, fetchNextPage, isFetchingNextPage } =
19
+ useSearchMessage({
20
+ query: searchTerm,
21
+ applicationType,
22
+ });
23
+
24
+ const renderItem = useCallback(
25
+ ({ item }: { item: ISearchMessageItem }) => {
26
+ return (
27
+ <SearchResultItem
28
+ item={item}
29
+ tabResultType={SearchConversationTabResultType.Messages}
30
+ onPressItem={onPressItem}
31
+ />
32
+ );
33
+ },
34
+ [onPressItem]
35
+ );
36
+
37
+ const keyExtractor = useCallback((item: ISearchMessageItem) => {
38
+ return `${item?.conversation?.conversationID}-${item?.chatLog?.clientMsgID}`;
39
+ }, []);
40
+
41
+ const onEndReached = useCallback(() => {
42
+ if (hasNextPage && !isFetchingNextPage) {
43
+ fetchNextPage();
44
+ }
45
+ }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
46
+
47
+ const ItemSeparatorComponent = useCallback(() => {
48
+ return <KDivider size={'hairline'} vertical={false} />;
49
+ }, []);
50
+
51
+ return (
52
+ <KContainer.View flex>
53
+ <FlatList
54
+ data={dataFlatten}
55
+ renderItem={renderItem}
56
+ keyExtractor={keyExtractor}
57
+ contentContainerStyle={styles.container}
58
+ showsVerticalScrollIndicator={false}
59
+ onEndReached={onEndReached}
60
+ ItemSeparatorComponent={ItemSeparatorComponent}
61
+ ListEmptyComponent={<SearchEmpty />}
62
+ />
63
+ </KContainer.View>
64
+ );
65
+ }
66
+ );
67
+
68
+ export default SearchMessagesResult;
69
+
70
+ const styles = StyleSheet.create({
71
+ container: {
72
+ paddingHorizontal: KSpacingValue['0.75rem'],
73
+ },
74
+ });
@@ -0,0 +1,107 @@
1
+ import {
2
+ KColors,
3
+ KContainer,
4
+ KImage,
5
+ KLabel,
6
+ KSpacingValue,
7
+ } from '@droppii/libs';
8
+ import { memo, useCallback } from 'react';
9
+ import type { ISearchMessageItem } from '../../types/chat';
10
+ import { StyleSheet } from 'react-native';
11
+ import { SearchConversationTabResultType } from './types';
12
+ import { AvatarSection, NamePrefixIcon } from '../../components/ThreadCard';
13
+ import { getLastMessageText } from '../../utils/messageUtils';
14
+ import { useUserStore } from '../../store';
15
+ import { formatTimestamp } from '../../utils/dateTimeUtils';
16
+
17
+ interface ISearchResultItemProps {
18
+ item: ISearchMessageItem;
19
+ tabResultType: SearchConversationTabResultType;
20
+ onPressItem?: (item: ISearchMessageItem) => void;
21
+ }
22
+ const SearchResultItem = memo((props: ISearchResultItemProps) => {
23
+ const { item, tabResultType, onPressItem } = props || {};
24
+ const currentUserId = useUserStore((s) => s.user?.userID);
25
+
26
+ const timestamp = formatTimestamp(item?.chatLog?.sendTime ?? null);
27
+ const lastMessageText = getLastMessageText(
28
+ item?.chatLog,
29
+ currentUserId,
30
+ item?.conversation
31
+ );
32
+
33
+ const handlePressItem = useCallback(() => {
34
+ onPressItem?.({ conversation: item?.conversation, chatLog: item?.chatLog });
35
+ }, [onPressItem, item?.conversation, item?.chatLog]);
36
+
37
+ return (
38
+ <KContainer.Touchable style={styles.container} onPress={handlePressItem}>
39
+ <KContainer.View style={styles.leftContainer}>
40
+ <AvatarSection
41
+ avatar={item?.conversation?.faceURL ?? null}
42
+ fullName={item?.conversation?.showName}
43
+ peerType={item?.conversation?.peerType}
44
+ size="nm"
45
+ />
46
+ <KContainer.View style={styles.contentContainer}>
47
+ <KContainer.View style={styles.messageContainer}>
48
+ <NamePrefixIcon peerType={item?.conversation?.peerType} />
49
+ <KLabel.Text typo="TextMdMedium" numberOfLines={1} flex>
50
+ {item?.conversation?.showName}
51
+ </KLabel.Text>
52
+ <KLabel.Text typo="TextXsNormal" color={KColors.gray.light}>
53
+ {timestamp}
54
+ </KLabel.Text>
55
+ </KContainer.View>
56
+ <KLabel.Text typo="TextNmNormal" numberOfLines={1} flex>
57
+ {lastMessageText}
58
+ </KLabel.Text>
59
+ </KContainer.View>
60
+ </KContainer.View>
61
+ <KContainer.VisibleView
62
+ visible={
63
+ tabResultType === SearchConversationTabResultType.Conversations
64
+ }
65
+ >
66
+ <KContainer.View style={styles.rightContainer}>
67
+ <KImage.VectorIcons
68
+ name="more-horizontal-b"
69
+ size={24}
70
+ color={KColors.gray.normal}
71
+ />
72
+ </KContainer.View>
73
+ </KContainer.VisibleView>
74
+ </KContainer.Touchable>
75
+ );
76
+ });
77
+
78
+ export default SearchResultItem;
79
+
80
+ const styles = StyleSheet.create({
81
+ container: {
82
+ flexDirection: 'row',
83
+ justifyContent: 'space-between',
84
+ alignItems: 'center',
85
+ gap: KSpacingValue['1rem'],
86
+ paddingVertical: KSpacingValue['0.5rem'],
87
+ },
88
+ leftContainer: {
89
+ flexDirection: 'row',
90
+ alignItems: 'center',
91
+ gap: KSpacingValue['0.5rem'],
92
+ flex: 1,
93
+ },
94
+ rightContainer: {
95
+ alignItems: 'center',
96
+ justifyContent: 'center',
97
+ },
98
+ contentContainer: {
99
+ flex: 1,
100
+ },
101
+ messageContainer: {
102
+ flexDirection: 'row',
103
+ alignItems: 'center',
104
+ gap: KSpacingValue['0.25rem'],
105
+ justifyContent: 'space-between',
106
+ },
107
+ });
@@ -0,0 +1,144 @@
1
+ import {
2
+ KColors,
3
+ KContainer,
4
+ KLabel,
5
+ KRadiusValue,
6
+ KSpacingValue,
7
+ } from '@droppii/libs';
8
+ import { memo, useCallback, useState } from 'react';
9
+ import { StyleSheet } from 'react-native';
10
+ import { TabView } from 'react-native-tab-view';
11
+ import { SearchConversationTabResultType } from './types';
12
+ import { trans } from '../../translation';
13
+ import SearchMessagesResult from './SearchMessagesResult';
14
+ import SearchAllResult from './SearchAllResult';
15
+ import type { ISearchMessageItem } from '../../types/chat';
16
+
17
+ type TabRoute = { key: string; title: string };
18
+
19
+ interface ISearchConversationTabResultProps {
20
+ searchTerm: string;
21
+ onPressItem?: (item: ISearchMessageItem) => void;
22
+ }
23
+
24
+ const TabRoutes: TabRoute[] = [
25
+ { key: SearchConversationTabResultType.All, title: trans('all') },
26
+ // { key: SearchConversationTabResultType.Conversations, title: trans('contacts') },
27
+ { key: SearchConversationTabResultType.Messages, title: trans('messages') },
28
+ ];
29
+
30
+ interface CustomTabBarProps {
31
+ routes: TabRoute[];
32
+ index: number;
33
+ onPress: (i: number) => void;
34
+ }
35
+
36
+ const CustomTabBar = memo(({ routes, index, onPress }: CustomTabBarProps) => (
37
+ <KContainer.View style={styles.tabRow}>
38
+ {routes.map((route, i) => {
39
+ const isActive = i === index;
40
+ return (
41
+ <KContainer.Touchable
42
+ key={route.key}
43
+ onPress={() => onPress(i)}
44
+ style={styles.tabItem}
45
+ activeOpacity={0.7}
46
+ >
47
+ <KLabel.Text
48
+ typo="TextNmBold"
49
+ color={isActive ? KColors.primary.normal : KColors.gray.light}
50
+ >
51
+ {route.title}
52
+ </KLabel.Text>
53
+ {isActive && <KContainer.View style={styles.indicator} />}
54
+ </KContainer.Touchable>
55
+ );
56
+ })}
57
+ </KContainer.View>
58
+ ));
59
+
60
+ const SearchConversationTabResult = memo(
61
+ ({ searchTerm, onPressItem }: ISearchConversationTabResultProps) => {
62
+ const [index, setIndex] = useState(0);
63
+ const [routes] = useState<TabRoute[]>(TabRoutes);
64
+
65
+ const handleChangeTab = useCallback(
66
+ (key: SearchConversationTabResultType) => {
67
+ setIndex(TabRoutes.findIndex((route) => route.key === key));
68
+ },
69
+ []
70
+ );
71
+
72
+ const renderScene = useCallback(
73
+ ({ route }: { route: TabRoute }) => {
74
+ switch (route.key) {
75
+ case SearchConversationTabResultType.All:
76
+ return (
77
+ <SearchAllResult
78
+ searchTerm={searchTerm}
79
+ handleChangeTab={handleChangeTab}
80
+ onPressItem={onPressItem}
81
+ />
82
+ );
83
+ case SearchConversationTabResultType.Conversations:
84
+ return null;
85
+ case SearchConversationTabResultType.Messages:
86
+ return (
87
+ <SearchMessagesResult
88
+ searchTerm={searchTerm}
89
+ onPressItem={onPressItem}
90
+ />
91
+ );
92
+ default:
93
+ return null;
94
+ }
95
+ },
96
+ [searchTerm, handleChangeTab, onPressItem]
97
+ );
98
+
99
+ const renderTabBar = useCallback(
100
+ () => <CustomTabBar routes={routes} index={index} onPress={setIndex} />,
101
+ [routes, index]
102
+ );
103
+
104
+ return (
105
+ <KContainer.View flex>
106
+ <TabView
107
+ navigationState={{ index, routes }}
108
+ renderScene={renderScene}
109
+ renderTabBar={renderTabBar}
110
+ onIndexChange={setIndex}
111
+ swipeEnabled={routes.length > 1}
112
+ lazy
113
+ />
114
+ </KContainer.View>
115
+ );
116
+ }
117
+ );
118
+
119
+ export default SearchConversationTabResult;
120
+
121
+ const styles = StyleSheet.create({
122
+ tabRow: {
123
+ flexDirection: 'row',
124
+ borderBottomWidth: 1,
125
+ borderBottomColor: KColors.border.normal,
126
+ },
127
+ tabItem: {
128
+ paddingVertical: KSpacingValue['0.5rem'],
129
+ height: 40,
130
+ marginHorizontal: KSpacingValue['0.75rem'],
131
+ justifyContent: 'center',
132
+ alignItems: 'center',
133
+ },
134
+ indicator: {
135
+ height: 3,
136
+ backgroundColor: KColors.primary.normal,
137
+ width: '100%',
138
+ borderRadius: KRadiusValue.x,
139
+ position: 'absolute',
140
+ bottom: -1,
141
+ left: 0,
142
+ right: 0,
143
+ },
144
+ });
@@ -0,0 +1,61 @@
1
+ import { KColors, KContainer, KNavigation } from '@droppii/libs';
2
+ import { memo, useRef, useState } from 'react';
3
+ import { useNavigation } from '@react-navigation/native';
4
+ import { trans } from '../../translation';
5
+ import debounce from 'lodash/debounce';
6
+ import type { TextInput } from 'react-native';
7
+ import SearchConversationTabResult from './TabResult';
8
+ import type { ISearchMessageItem } from '../../types/chat';
9
+
10
+ interface SearchConversationScreenProps {
11
+ onPressItem?: (item: ISearchMessageItem) => void;
12
+ }
13
+ const SearchConversationScreen = memo(
14
+ ({ onPressItem }: SearchConversationScreenProps) => {
15
+ const nav = useNavigation();
16
+ const [searchTerm, setSearchTerm] = useState('');
17
+ const searchInputRef = useRef<TextInput>(null);
18
+
19
+ const debouncedSearch = debounce(
20
+ (text: string) => {
21
+ setSearchTerm(text);
22
+ },
23
+ 500,
24
+ { leading: false, trailing: true }
25
+ );
26
+
27
+ return (
28
+ <KContainer.Page flex background={KColors.white} edges={['bottom']}>
29
+ <KNavigation.HeaderV2Body
30
+ theme="light"
31
+ size="large"
32
+ isNested
33
+ leftButton={{
34
+ id: 'back',
35
+ icon: { vectorName: 'arrow-left-o' },
36
+ onPress: nav.goBack,
37
+ }}
38
+ searchProps={{
39
+ position: 'center',
40
+ placeholder: trans('search'),
41
+ onChangeText: debouncedSearch,
42
+ ref: searchInputRef as any,
43
+ returnKeyType: 'search',
44
+ value: searchTerm,
45
+ autoFocus: true,
46
+ background: KColors.palette.gray.w25,
47
+ br: '2x',
48
+ searchIconLeftColor: KColors.gray.normal,
49
+ marginL: undefined,
50
+ }}
51
+ />
52
+ <SearchConversationTabResult
53
+ searchTerm={searchTerm}
54
+ onPressItem={onPressItem}
55
+ />
56
+ </KContainer.Page>
57
+ );
58
+ }
59
+ );
60
+
61
+ export default SearchConversationScreen;