@droppii-org/chat-mobile 0.2.79 → 0.2.81

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 (187) hide show
  1. package/lib/module/components/messages/BubbleTail.js +31 -0
  2. package/lib/module/components/messages/BubbleTail.js.map +1 -0
  3. package/lib/module/components/messages/BubbleTailRow.js +46 -0
  4. package/lib/module/components/messages/BubbleTailRow.js.map +1 -0
  5. package/lib/module/components/messages/bubbleTail.utils.js +19 -0
  6. package/lib/module/components/messages/bubbleTail.utils.js.map +1 -0
  7. package/lib/module/components/messages/fileMessage/index.js +72 -65
  8. package/lib/module/components/messages/fileMessage/index.js.map +1 -1
  9. package/lib/module/components/messages/imageMessage/index.js +23 -17
  10. package/lib/module/components/messages/imageMessage/index.js.map +1 -1
  11. package/lib/module/components/messages/linkMessage/index.js +11 -23
  12. package/lib/module/components/messages/linkMessage/index.js.map +1 -1
  13. package/lib/module/components/messages/mergedMessage/index.js +14 -7
  14. package/lib/module/components/messages/mergedMessage/index.js.map +1 -1
  15. package/lib/module/components/messages/quotedMessage/index.js +11 -23
  16. package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
  17. package/lib/module/components/messages/revokedMessage/index.js +14 -7
  18. package/lib/module/components/messages/revokedMessage/index.js.map +1 -1
  19. package/lib/module/components/messages/stickerMessage/index.js +13 -7
  20. package/lib/module/components/messages/stickerMessage/index.js.map +1 -1
  21. package/lib/module/components/messages/textMessage/index.js +11 -26
  22. package/lib/module/components/messages/textMessage/index.js.map +1 -1
  23. package/lib/module/components/messages/types.js.map +1 -1
  24. package/lib/module/components/messages/videoMessage/index.js +16 -8
  25. package/lib/module/components/messages/videoMessage/index.js.map +1 -1
  26. package/lib/module/config/api-endpoints.js +2 -1
  27. package/lib/module/config/api-endpoints.js.map +1 -1
  28. package/lib/module/hooks/contacts/useSearchContacts.js +50 -0
  29. package/lib/module/hooks/contacts/useSearchContacts.js.map +1 -0
  30. package/lib/module/hooks/groups/useDismissGroup.js +2 -1
  31. package/lib/module/hooks/groups/useDismissGroup.js.map +1 -1
  32. package/lib/module/hooks/query-keys.js +4 -0
  33. package/lib/module/hooks/query-keys.js.map +1 -1
  34. package/lib/module/screens/SearchConversation/ContactResultItem.js +119 -0
  35. package/lib/module/screens/SearchConversation/ContactResultItem.js.map +1 -0
  36. package/lib/module/screens/SearchConversation/SearchAllResult.js +26 -20
  37. package/lib/module/screens/SearchConversation/SearchAllResult.js.map +1 -1
  38. package/lib/module/screens/SearchConversation/SearchCommunityResult.js +79 -0
  39. package/lib/module/screens/SearchConversation/SearchCommunityResult.js.map +1 -0
  40. package/lib/module/screens/SearchConversation/SearchContactsResult.js +117 -0
  41. package/lib/module/screens/SearchConversation/SearchContactsResult.js.map +1 -0
  42. package/lib/module/screens/SearchConversation/SearchEmpty.js +2 -0
  43. package/lib/module/screens/SearchConversation/SearchEmpty.js.map +1 -1
  44. package/lib/module/screens/SearchConversation/SearchMessagesResult.js +1 -8
  45. package/lib/module/screens/SearchConversation/SearchMessagesResult.js.map +1 -1
  46. package/lib/module/screens/SearchConversation/SearchResultItem.js +2 -0
  47. package/lib/module/screens/SearchConversation/SearchResultItem.js.map +1 -1
  48. package/lib/module/screens/SearchConversation/TabResult.js +24 -7
  49. package/lib/module/screens/SearchConversation/TabResult.js.map +1 -1
  50. package/lib/module/screens/SearchConversation/index.js +6 -2
  51. package/lib/module/screens/SearchConversation/index.js.map +1 -1
  52. package/lib/module/screens/SearchConversation/types.js +1 -0
  53. package/lib/module/screens/SearchConversation/types.js.map +1 -1
  54. package/lib/module/screens/chat-detail/ChatListLegend.js +71 -21
  55. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  56. package/lib/module/screens/chat-detail/legend/LegendChatDay.js +8 -2
  57. package/lib/module/screens/chat-detail/legend/LegendChatDay.js.map +1 -1
  58. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +38 -13
  59. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  60. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js +11 -1
  61. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js.map +1 -1
  62. package/lib/module/services/endpoints.js +2 -1
  63. package/lib/module/services/endpoints.js.map +1 -1
  64. package/lib/module/translation/resources/i18n.js +6 -0
  65. package/lib/module/translation/resources/i18n.js.map +1 -1
  66. package/lib/module/types/chat.js +8 -0
  67. package/lib/module/types/chat.js.map +1 -1
  68. package/lib/module/utils/dateTimeUtils.js +10 -0
  69. package/lib/module/utils/dateTimeUtils.js.map +1 -1
  70. package/lib/module/utils/highlightSearch.js +6 -2
  71. package/lib/module/utils/highlightSearch.js.map +1 -1
  72. package/lib/module/utils/legendListMessage.js +86 -12
  73. package/lib/module/utils/legendListMessage.js.map +1 -1
  74. package/lib/module/utils/message.js +19 -1
  75. package/lib/module/utils/message.js.map +1 -1
  76. package/lib/module/utils/messageUtils.js +16 -2
  77. package/lib/module/utils/messageUtils.js.map +1 -1
  78. package/lib/typescript/src/components/messages/BubbleTail.d.ts +13 -0
  79. package/lib/typescript/src/components/messages/BubbleTail.d.ts.map +1 -0
  80. package/lib/typescript/src/components/messages/BubbleTailRow.d.ts +25 -0
  81. package/lib/typescript/src/components/messages/BubbleTailRow.d.ts.map +1 -0
  82. package/lib/typescript/src/components/messages/bubbleTail.utils.d.ts +11 -0
  83. package/lib/typescript/src/components/messages/bubbleTail.utils.d.ts.map +1 -0
  84. package/lib/typescript/src/components/messages/fileMessage/index.d.ts +1 -1
  85. package/lib/typescript/src/components/messages/fileMessage/index.d.ts.map +1 -1
  86. package/lib/typescript/src/components/messages/imageMessage/index.d.ts +1 -1
  87. package/lib/typescript/src/components/messages/imageMessage/index.d.ts.map +1 -1
  88. package/lib/typescript/src/components/messages/linkMessage/index.d.ts +1 -1
  89. package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
  90. package/lib/typescript/src/components/messages/mergedMessage/index.d.ts +1 -1
  91. package/lib/typescript/src/components/messages/mergedMessage/index.d.ts.map +1 -1
  92. package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
  93. package/lib/typescript/src/components/messages/revokedMessage/index.d.ts +1 -1
  94. package/lib/typescript/src/components/messages/revokedMessage/index.d.ts.map +1 -1
  95. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts +1 -1
  96. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts.map +1 -1
  97. package/lib/typescript/src/components/messages/textMessage/index.d.ts +1 -1
  98. package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
  99. package/lib/typescript/src/components/messages/types.d.ts +3 -0
  100. package/lib/typescript/src/components/messages/types.d.ts.map +1 -1
  101. package/lib/typescript/src/components/messages/videoMessage/index.d.ts +1 -1
  102. package/lib/typescript/src/components/messages/videoMessage/index.d.ts.map +1 -1
  103. package/lib/typescript/src/config/api-endpoints.d.ts +1 -0
  104. package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
  105. package/lib/typescript/src/hooks/contacts/useSearchContacts.d.ts +221 -0
  106. package/lib/typescript/src/hooks/contacts/useSearchContacts.d.ts.map +1 -0
  107. package/lib/typescript/src/hooks/groups/useDismissGroup.d.ts.map +1 -1
  108. package/lib/typescript/src/hooks/query-keys.d.ts +4 -0
  109. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  110. package/lib/typescript/src/screens/SearchConversation/ContactResultItem.d.ts +9 -0
  111. package/lib/typescript/src/screens/SearchConversation/ContactResultItem.d.ts.map +1 -0
  112. package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts +3 -2
  113. package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts.map +1 -1
  114. package/lib/typescript/src/screens/SearchConversation/SearchCommunityResult.d.ts +7 -0
  115. package/lib/typescript/src/screens/SearchConversation/SearchCommunityResult.d.ts.map +1 -0
  116. package/lib/typescript/src/screens/SearchConversation/SearchContactsResult.d.ts +8 -0
  117. package/lib/typescript/src/screens/SearchConversation/SearchContactsResult.d.ts.map +1 -0
  118. package/lib/typescript/src/screens/SearchConversation/SearchEmpty.d.ts.map +1 -1
  119. package/lib/typescript/src/screens/SearchConversation/SearchMessagesResult.d.ts.map +1 -1
  120. package/lib/typescript/src/screens/SearchConversation/SearchResultItem.d.ts.map +1 -1
  121. package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts +4 -2
  122. package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts.map +1 -1
  123. package/lib/typescript/src/screens/SearchConversation/index.d.ts +4 -2
  124. package/lib/typescript/src/screens/SearchConversation/index.d.ts.map +1 -1
  125. package/lib/typescript/src/screens/SearchConversation/types.d.ts +2 -1
  126. package/lib/typescript/src/screens/SearchConversation/types.d.ts.map +1 -1
  127. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  128. package/lib/typescript/src/screens/chat-detail/legend/LegendChatDay.d.ts +6 -1
  129. package/lib/typescript/src/screens/chat-detail/legend/LegendChatDay.d.ts.map +1 -1
  130. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +14 -1
  131. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  132. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts.map +1 -1
  133. package/lib/typescript/src/services/endpoints.d.ts +1 -0
  134. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  135. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  136. package/lib/typescript/src/types/chat.d.ts +27 -0
  137. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  138. package/lib/typescript/src/utils/dateTimeUtils.d.ts +2 -0
  139. package/lib/typescript/src/utils/dateTimeUtils.d.ts.map +1 -1
  140. package/lib/typescript/src/utils/highlightSearch.d.ts.map +1 -1
  141. package/lib/typescript/src/utils/legendListMessage.d.ts +19 -4
  142. package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -1
  143. package/lib/typescript/src/utils/message.d.ts +17 -0
  144. package/lib/typescript/src/utils/message.d.ts.map +1 -1
  145. package/lib/typescript/src/utils/messageUtils.d.ts +1 -0
  146. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  147. package/package.json +3 -1
  148. package/src/build-ignore.d.ts +2 -0
  149. package/src/components/messages/BubbleTail.tsx +35 -0
  150. package/src/components/messages/BubbleTailRow.tsx +64 -0
  151. package/src/components/messages/bubbleTail.utils.ts +21 -0
  152. package/src/components/messages/fileMessage/index.tsx +90 -83
  153. package/src/components/messages/imageMessage/index.tsx +26 -23
  154. package/src/components/messages/linkMessage/index.tsx +6 -18
  155. package/src/components/messages/mergedMessage/index.tsx +27 -15
  156. package/src/components/messages/quotedMessage/index.tsx +7 -19
  157. package/src/components/messages/revokedMessage/index.tsx +23 -14
  158. package/src/components/messages/stickerMessage/index.tsx +16 -11
  159. package/src/components/messages/textMessage/index.tsx +6 -22
  160. package/src/components/messages/types.ts +3 -0
  161. package/src/components/messages/videoMessage/index.tsx +13 -10
  162. package/src/config/api-endpoints.ts +2 -0
  163. package/src/hooks/contacts/useSearchContacts.ts +66 -0
  164. package/src/hooks/groups/useDismissGroup.tsx +1 -0
  165. package/src/hooks/query-keys.ts +6 -0
  166. package/src/screens/SearchConversation/ContactResultItem.tsx +128 -0
  167. package/src/screens/SearchConversation/SearchAllResult.tsx +38 -24
  168. package/src/screens/SearchConversation/SearchCommunityResult.tsx +96 -0
  169. package/src/screens/SearchConversation/SearchContactsResult.tsx +155 -0
  170. package/src/screens/SearchConversation/SearchEmpty.tsx +2 -0
  171. package/src/screens/SearchConversation/SearchMessagesResult.tsx +1 -6
  172. package/src/screens/SearchConversation/SearchResultItem.tsx +6 -1
  173. package/src/screens/SearchConversation/TabResult.tsx +40 -5
  174. package/src/screens/SearchConversation/index.tsx +10 -2
  175. package/src/screens/SearchConversation/types.tsx +1 -0
  176. package/src/screens/chat-detail/ChatListLegend.tsx +97 -28
  177. package/src/screens/chat-detail/legend/LegendChatDay.tsx +25 -15
  178. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +62 -12
  179. package/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx +22 -4
  180. package/src/services/endpoints.ts +1 -0
  181. package/src/translation/resources/i18n.ts +6 -0
  182. package/src/types/chat.ts +30 -0
  183. package/src/utils/dateTimeUtils.ts +15 -0
  184. package/src/utils/highlightSearch.ts +12 -2
  185. package/src/utils/legendListMessage.ts +123 -14
  186. package/src/utils/message.ts +19 -1
  187. package/src/utils/messageUtils.ts +23 -9
@@ -6,6 +6,7 @@ import ChatImageDimens, {
6
6
  } from '../../../utils/chatImageDimens';
7
7
  import { CHAT_BUBBLE_COLORS } from '../../../screens/chat-detail/constants';
8
8
  import { messageStyles } from '../styles';
9
+ import { BubbleTailRow } from '../BubbleTailRow';
9
10
  import type { BaseLegendMessageProps } from '../types';
10
11
  const formatDuration = (durationMs: number): string => {
11
12
  const totalSeconds = Math.floor(durationMs / 1000);
@@ -15,7 +16,7 @@ const formatDuration = (durationMs: number): string => {
15
16
  };
16
17
 
17
18
  export const VideoMessage = memo(
18
- ({ message, onMediaPress }: BaseLegendMessageProps) => {
19
+ ({ message, isOutgoing, onMediaPress }: BaseLegendMessageProps) => {
19
20
  const videoElem = message?.videoElem;
20
21
  const videoUrl = videoElem?.videoUrl;
21
22
  const duration = videoElem?.duration;
@@ -29,13 +30,15 @@ export const VideoMessage = memo(
29
30
 
30
31
  if (!videoUrl) {
31
32
  return (
32
- <KContainer.View
33
- style={[messageStyles.bubble, messageStyles.videoPlaceholder]}
34
- >
35
- <KLabel.Text typo="TextSmNormal" color={CHAT_BUBBLE_COLORS.text}>
36
- 📹 [Video unavailable]
37
- </KLabel.Text>
38
- </KContainer.View>
33
+ <BubbleTailRow isOutgoing={isOutgoing} withTail={false}>
34
+ <KContainer.View
35
+ style={[messageStyles.bubble, messageStyles.videoPlaceholder]}
36
+ >
37
+ <KLabel.Text typo="TextSmNormal" color={CHAT_BUBBLE_COLORS.text}>
38
+ 📹 [Video unavailable]
39
+ </KLabel.Text>
40
+ </KContainer.View>
41
+ </BubbleTailRow>
39
42
  );
40
43
  }
41
44
 
@@ -44,7 +47,7 @@ export const VideoMessage = memo(
44
47
  );
45
48
 
46
49
  return (
47
- <>
50
+ <BubbleTailRow isOutgoing={isOutgoing} withTail={false}>
48
51
  <KContainer.Touchable
49
52
  onPress={handlePress}
50
53
  style={[
@@ -80,7 +83,7 @@ export const VideoMessage = memo(
80
83
  </KContainer.View>
81
84
  )}
82
85
  </KContainer.Touchable>
83
- </>
86
+ </BubbleTailRow>
84
87
  );
85
88
  }
86
89
  );
@@ -22,6 +22,7 @@ export interface ApiEndpoints {
22
22
  stickerHistory: string;
23
23
  messagesResources: string;
24
24
  messages: string;
25
+ contacts: string;
25
26
  }
26
27
 
27
28
  const DEFAULT_ENDPOINTS: ApiEndpoints = {
@@ -35,6 +36,7 @@ const DEFAULT_ENDPOINTS: ApiEndpoints = {
35
36
  stickerHistory: '/chat-service/v1/app/stickers/history',
36
37
  messagesResources: '/chat-service/v1/app/messages/resources',
37
38
  messages: '/chat-service/v1/app/messages',
39
+ contacts: '/chat-service/v1/app/contacts',
38
40
  };
39
41
 
40
42
  let endpoints: ApiEndpoints = { ...DEFAULT_ENDPOINTS };
@@ -0,0 +1,66 @@
1
+ import { useInfiniteQuery } from '@tanstack/react-query';
2
+ import { useMemo } from 'react';
3
+ import { contactQueryKeys } from '../query-keys';
4
+ import { apiInstance } from '../../services/apis';
5
+ import { ENDPOINTS } from '../../services/endpoints';
6
+ import type {
7
+ ContactCategory,
8
+ DChatApplicationType,
9
+ IContactSearchItem,
10
+ IContactSearchRequest,
11
+ } from '../../types/chat';
12
+ import type { BaseResponse } from '../../types/auth';
13
+ import { PAGE_SIZE } from '../../constants';
14
+
15
+ interface IUseSearchContactsProps {
16
+ query: string;
17
+ applicationType: DChatApplicationType;
18
+ type?: ContactCategory;
19
+ pageSize?: number;
20
+ }
21
+
22
+ export const useSearchContacts = ({
23
+ query,
24
+ applicationType,
25
+ type,
26
+ pageSize = PAGE_SIZE,
27
+ }: IUseSearchContactsProps) => {
28
+ const { data, ...rest } = useInfiniteQuery({
29
+ queryKey: [contactQueryKeys.search(query.trim(), type), pageSize],
30
+ queryFn: async ({ pageParam = 1 }) => {
31
+ const params: IContactSearchRequest = {
32
+ page: pageParam,
33
+ pageSize,
34
+ applicationType,
35
+ searchTerm: query.trim(),
36
+ type,
37
+ };
38
+ const res = await apiInstance?.post<BaseResponse<IContactSearchItem[]>>(
39
+ ENDPOINTS.chatService.searchContacts(),
40
+ params
41
+ );
42
+ return res?.data;
43
+ },
44
+ getNextPageParam: (lastPage) => {
45
+ const currentPage = lastPage?.pageable?.pageNumber || 1;
46
+ const dataLength = lastPage?.data?.length || 0;
47
+ return dataLength < pageSize ? undefined : currentPage + 1;
48
+ },
49
+ initialPageParam: 1,
50
+ enabled: !!query.trim() && !!applicationType,
51
+ });
52
+
53
+ const dataFlatten = useMemo<IContactSearchItem[]>(() => {
54
+ if (!query.trim() || !data) return [];
55
+ return (
56
+ data?.pages
57
+ ?.flatMap?.((page) => page?.data)
58
+ ?.filter((item): item is IContactSearchItem => item !== undefined) || []
59
+ );
60
+ }, [data, query]);
61
+
62
+ return {
63
+ dataFlatten,
64
+ ...rest,
65
+ };
66
+ };
@@ -14,6 +14,7 @@ export function useDismissGroup({ onSuccess }: UseDismissGroupProps) {
14
14
  header: {
15
15
  title: { text: trans('group:dismiss_confirm_title') },
16
16
  alignment: 'center',
17
+ showCloseButton: true,
17
18
  },
18
19
  body: {
19
20
  renderContent: () => {
@@ -62,6 +62,12 @@ export const messageQueryKeys = {
62
62
  [...messageQueryKeys.all, 'search', query, conversationId] as const,
63
63
  };
64
64
 
65
+ export const contactQueryKeys = {
66
+ all: ['contacts'] as const,
67
+ search: (query: string, type?: string) =>
68
+ [...contactQueryKeys.all, 'search', query, type] as const,
69
+ };
70
+
65
71
  export const groupQueryKeys = {
66
72
  all: ['groups'] as const,
67
73
  public: () => [...groupQueryKeys.all, 'public'] as const,
@@ -0,0 +1,128 @@
1
+ import {
2
+ KColors,
3
+ KContainer,
4
+ KImage,
5
+ KLabel,
6
+ KSpacingValue,
7
+ } from '@droppii/libs';
8
+ import { memo, useCallback, useMemo } from 'react';
9
+ import { StyleSheet } from 'react-native';
10
+ import { PeerType } from '@droppii/openim-rn-client-sdk';
11
+ import { ContactCategory, type IContactSearchItem } from '../../types/chat';
12
+ import { AvatarSection, NamePrefixIcon } from '../../components/ThreadCard';
13
+ import { formatLastOnlineTime } from '../../utils/dateTimeUtils';
14
+ import {
15
+ highlightSearch,
16
+ type HighlightResult,
17
+ } from '../../utils/highlightSearch';
18
+ import { trans } from '../../translation';
19
+ import isEmpty from 'lodash/isEmpty';
20
+
21
+ const CONTACT_PEER_TYPE: Record<ContactCategory, PeerType> = {
22
+ [ContactCategory.Customer]: PeerType.Customer,
23
+ [ContactCategory.Seller]: PeerType.User,
24
+ [ContactCategory.Pdp]: PeerType.Bot,
25
+ [ContactCategory.Group]: PeerType.Group,
26
+ };
27
+
28
+ interface IContactResultItemProps {
29
+ item: IContactSearchItem;
30
+ onPressSearchContact?: (item: IContactSearchItem) => void;
31
+ searchTerm?: string;
32
+ }
33
+
34
+ const ContactResultItem = memo((props: IContactResultItemProps) => {
35
+ const { item, onPressSearchContact, searchTerm } = props || {};
36
+ const peerType = CONTACT_PEER_TYPE[item?.type];
37
+
38
+ const subtitle = useMemo(() => {
39
+ if (item?.type === ContactCategory.Group) {
40
+ const description = !isEmpty(item?.description?.trim())
41
+ ? item?.description
42
+ : trans('group:no_description');
43
+ return `${trans('group:member_count', { count: item?.memberCount ?? 0 })} • ${description}`;
44
+ }
45
+ if (item?.type === ContactCategory.Pdp) {
46
+ return '';
47
+ }
48
+ return formatLastOnlineTime(item?.lastOnlineTime ?? null);
49
+ }, [item?.type, item?.description, item?.memberCount, item?.lastOnlineTime]);
50
+
51
+ const highlightResult: HighlightResult | null = useMemo(() => {
52
+ if (isEmpty(searchTerm?.trim())) return null;
53
+ return highlightSearch(item?.targetName || '', searchTerm || '');
54
+ }, [item?.targetName, searchTerm]);
55
+
56
+ const handlePressItem = useCallback(() => {
57
+ onPressSearchContact?.(item);
58
+ }, [onPressSearchContact, item]);
59
+
60
+ return (
61
+ <KContainer.Touchable
62
+ row
63
+ justifyContent="space-between"
64
+ alignItems
65
+ paddingV={'0.75rem'}
66
+ style={styles.container}
67
+ onPress={handlePressItem}
68
+ brBW={1}
69
+ brBC={KColors.border.light}
70
+ >
71
+ <KContainer.View row alignItems flex>
72
+ <AvatarSection
73
+ avatar={item?.targetAvatar ?? null}
74
+ fullName={item?.targetName}
75
+ peerType={peerType}
76
+ size="nm"
77
+ />
78
+ <KContainer.View flex marginL={'0.5rem'}>
79
+ <KContainer.View row alignItems>
80
+ <NamePrefixIcon peerType={peerType} />
81
+ {highlightResult != null ? (
82
+ <KLabel.Text typo="TextMdMedium" numberOfLines={1} flex>
83
+ {highlightResult?.before || ''}
84
+ <KLabel.Text typo="TextMdMedium" color={KColors.primary.normal}>
85
+ {highlightResult?.match || ''}
86
+ </KLabel.Text>
87
+ <KLabel.Text typo="TextMdMedium">
88
+ {highlightResult?.after || ''}
89
+ </KLabel.Text>
90
+ </KLabel.Text>
91
+ ) : (
92
+ <KLabel.Text typo="TextMdMedium" numberOfLines={1} flex>
93
+ {item?.targetName || ''}
94
+ </KLabel.Text>
95
+ )}
96
+ </KContainer.View>
97
+ <KContainer.VisibleView visible={!!subtitle}>
98
+ <KLabel.Text
99
+ typo="TextXsNormal"
100
+ color={KColors.gray.light}
101
+ numberOfLines={1}
102
+ >
103
+ {subtitle}
104
+ </KLabel.Text>
105
+ </KContainer.VisibleView>
106
+ </KContainer.View>
107
+ </KContainer.View>
108
+
109
+ <KContainer.VisibleView visible={false}>
110
+ <KContainer.View center>
111
+ <KImage.VectorIcons
112
+ name="more-horizontal-b"
113
+ size={24}
114
+ color={KColors.gray.normal}
115
+ />
116
+ </KContainer.View>
117
+ </KContainer.VisibleView>
118
+ </KContainer.Touchable>
119
+ );
120
+ });
121
+
122
+ export default ContactResultItem;
123
+
124
+ const styles = StyleSheet.create({
125
+ container: {
126
+ gap: KSpacingValue['1rem'],
127
+ },
128
+ });
@@ -10,41 +10,56 @@ import {
10
10
  import { memo, useCallback, useMemo } from 'react';
11
11
  import { useChatContext } from '../../context';
12
12
  import { useSearchMessage } from '../../hooks/message/useSearchMessage';
13
+ import { useSearchContacts } from '../../hooks/contacts/useSearchContacts';
13
14
  import { SearchConversationTabResultType } from './types';
14
- import type { ISearchMessageItem } from '../../types/chat';
15
+ import type { IContactSearchItem, ISearchMessageItem } from '../../types/chat';
15
16
  import { trans } from '../../translation';
16
17
  import { ActivityIndicator, FlatList, StyleSheet } from 'react-native';
17
18
  import { SearchEmpty } from './SearchEmpty';
18
19
  import SearchResultItem from './SearchResultItem';
20
+ import ContactResultItem from './ContactResultItem';
19
21
 
20
22
  interface SearchAllResultProps {
21
23
  searchTerm: string;
22
24
  handleChangeTab: (key: SearchConversationTabResultType) => void;
23
25
  onPressItem?: (item: ISearchMessageItem) => void;
26
+ onPressSearchContact?: (item: IContactSearchItem) => void;
24
27
  }
25
28
 
26
29
  interface ISearchAllResultItem {
27
30
  type: SearchConversationTabResultType;
28
- data: ISearchMessageItem[];
31
+ data: (ISearchMessageItem | IContactSearchItem)[];
29
32
  title: string;
30
33
  }
31
34
 
32
35
  const PAGE_SIZE = 5;
33
36
 
34
37
  const SearchAllResult = memo(
35
- ({ searchTerm, handleChangeTab, onPressItem }: SearchAllResultProps) => {
38
+ ({
39
+ searchTerm,
40
+ handleChangeTab,
41
+ onPressItem,
42
+ onPressSearchContact,
43
+ }: SearchAllResultProps) => {
36
44
  const { applicationType } = useChatContext();
37
- const { dataFlatten: messagesData, isFetching } = useSearchMessage({
38
- query: searchTerm,
39
- applicationType,
40
- pageSize: PAGE_SIZE,
41
- });
45
+ const { dataFlatten: messagesData, isFetching: isFetchingMessages } =
46
+ useSearchMessage({
47
+ query: searchTerm,
48
+ applicationType,
49
+ pageSize: PAGE_SIZE,
50
+ });
51
+ const { dataFlatten: contactsData, isFetching: isFetchingContacts } =
52
+ useSearchContacts({
53
+ query: searchTerm,
54
+ applicationType,
55
+ });
56
+ const isFetching = isFetchingMessages || isFetchingContacts;
42
57
 
43
58
  const data: ISearchAllResultItem[] = useMemo(() => {
44
59
  const mData = [
45
60
  {
46
61
  type: SearchConversationTabResultType.Conversations,
47
- data: [],
62
+ data: contactsData,
48
63
  title: trans('contacts'),
49
64
  },
50
65
  {
@@ -54,7 +69,7 @@ const SearchAllResult = memo(
54
69
  },
55
70
  ]?.filter((item) => item.data.length > 0);
56
71
  return mData;
57
- }, [messagesData]);
72
+ }, [messagesData, contactsData]);
58
73
 
59
74
  const onChangeTab = useCallback(
60
75
  (key: SearchConversationTabResultType) => {
@@ -80,21 +95,24 @@ const SearchAllResult = memo(
80
95
  </KLabel.Text>
81
96
  <KImage.VectorIcons name="angle-right-o" size={20} />
82
97
  </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
- >
98
+ {item.type === SearchConversationTabResultType.Conversations
99
+ ? (item.data as IContactSearchItem[]).map((childItem) => (
100
+ <ContactResultItem
101
+ key={`${item.type}-${childItem?.id}`}
102
+ item={childItem}
103
+ onPressSearchContact={onPressSearchContact}
104
+ searchTerm={searchTerm}
105
+ />
106
+ ))
107
+ : (item.data as ISearchMessageItem[]).map((childItem) => (
89
108
  <SearchResultItem
109
+ key={`${item.type}-${childItem?.conversation?.conversationID}-${childItem?.chatLog?.clientMsgID}`}
90
110
  item={childItem}
91
111
  tabResultType={item.type}
92
112
  onPressItem={onPressItem}
93
113
  searchTerm={searchTerm}
94
114
  />
95
- </KContainer.View>
96
- );
97
- })}
115
+ ))}
98
116
  <KButton.Link
99
117
  label={trans('view_all')}
100
118
  icon={{
@@ -108,7 +126,7 @@ const SearchAllResult = memo(
108
126
  </KContainer.View>
109
127
  );
110
128
  },
111
- [onChangeTab, onPressItem, searchTerm]
129
+ [onChangeTab, onPressItem, onPressSearchContact, searchTerm]
112
130
  );
113
131
 
114
132
  const keyExtractor = useCallback((item: ISearchAllResultItem) => {
@@ -158,10 +176,6 @@ const styles = StyleSheet.create({
158
176
  borderBottomWidth: 1,
159
177
  borderBottomColor: KColors.border.light,
160
178
  },
161
- childItemContainer: {
162
- borderBottomWidth: 1,
163
- borderBottomColor: KColors.border.light,
164
- },
165
179
  viewAllButton: {
166
180
  height: 40,
167
181
  },
@@ -0,0 +1,96 @@
1
+ import { KColors, KContainer, KSpacingValue } from '@droppii/libs';
2
+ import { memo, useCallback, useMemo } from 'react';
3
+ import { ActivityIndicator, FlatList, StyleSheet } from 'react-native';
4
+ import isEmpty from 'lodash/isEmpty';
5
+ import type { SearchPublicGroupInfo } from '@droppii/openim-rn-client-sdk';
6
+ import { useFetchPublicGroup } from '../../hooks/groups/useFetchPublicGroup';
7
+ import { GroupItem } from '../../components/GroupItem';
8
+ import { SearchEmpty } from './SearchEmpty';
9
+ import { SearchConversationTabResultType } from './types';
10
+
11
+ interface ISearchCommunityResultProps {
12
+ searchTerm: string;
13
+ onPressGroup?: (groupId: string) => void;
14
+ }
15
+
16
+ const keyExtractor = (item: SearchPublicGroupInfo) => item.groupID;
17
+
18
+ const SearchCommunityResult = memo(
19
+ ({ searchTerm, onPressGroup }: ISearchCommunityResultProps) => {
20
+ const hasKeyword = !isEmpty(searchTerm?.trim());
21
+
22
+ const {
23
+ dataFlatten,
24
+ hasNextPage,
25
+ fetchNextPage,
26
+ isFetchingNextPage,
27
+ isFetching,
28
+ isRefetching,
29
+ refetch,
30
+ } = useFetchPublicGroup(searchTerm);
31
+
32
+ const data = useMemo(
33
+ () => (hasKeyword ? dataFlatten : []),
34
+ [hasKeyword, dataFlatten]
35
+ );
36
+
37
+ const renderItem = useCallback(
38
+ ({ item }: { item: SearchPublicGroupInfo }) => (
39
+ <GroupItem item={item} onPress={onPressGroup} />
40
+ ),
41
+ [onPressGroup]
42
+ );
43
+
44
+ const onEndReached = useCallback(() => {
45
+ if (hasKeyword && hasNextPage && !isFetchingNextPage) {
46
+ fetchNextPage();
47
+ }
48
+ }, [hasKeyword, hasNextPage, isFetchingNextPage, fetchNextPage]);
49
+
50
+ if (!hasKeyword) {
51
+ return (
52
+ <KContainer.View flex>
53
+ <SearchEmpty
54
+ searchTerm={searchTerm}
55
+ searchType={SearchConversationTabResultType.Community}
56
+ />
57
+ </KContainer.View>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <KContainer.View flex>
63
+ {isFetching && !isFetchingNextPage && !isRefetching ? (
64
+ <KContainer.View flex center>
65
+ <ActivityIndicator color={KColors.primary.normal} size={'small'} />
66
+ </KContainer.View>
67
+ ) : (
68
+ <FlatList
69
+ data={data}
70
+ renderItem={renderItem}
71
+ keyExtractor={keyExtractor}
72
+ contentContainerStyle={styles.container}
73
+ showsVerticalScrollIndicator={false}
74
+ onEndReached={onEndReached}
75
+ ListEmptyComponent={
76
+ <SearchEmpty
77
+ searchTerm={searchTerm}
78
+ searchType={SearchConversationTabResultType.Community}
79
+ />
80
+ }
81
+ refreshing={isFetching}
82
+ onRefresh={refetch}
83
+ />
84
+ )}
85
+ </KContainer.View>
86
+ );
87
+ }
88
+ );
89
+
90
+ export default SearchCommunityResult;
91
+
92
+ const styles = StyleSheet.create({
93
+ container: {
94
+ paddingHorizontal: KSpacingValue['0.75rem'],
95
+ },
96
+ });
@@ -0,0 +1,155 @@
1
+ import {
2
+ KColors,
3
+ KContainer,
4
+ KSpacingValue,
5
+ KTagsChoiceSlideBar,
6
+ type KTagsChoiceSlideBarItemProps,
7
+ } from '@droppii/libs';
8
+ import { memo, useCallback, useMemo, useState } from 'react';
9
+ import { ActivityIndicator, FlatList, StyleSheet } from 'react-native';
10
+ import { useChatContext } from '../../context';
11
+ import { useSearchContacts } from '../../hooks/contacts/useSearchContacts';
12
+ import { ContactCategory, type IContactSearchItem } from '../../types/chat';
13
+ import { trans } from '../../translation';
14
+ import { SearchEmpty } from './SearchEmpty';
15
+ import ContactResultItem from './ContactResultItem';
16
+ import { SearchConversationTabResultType } from './types';
17
+
18
+ interface ISearchContactsResultProps {
19
+ searchTerm: string;
20
+ onPressSearchContact?: (item: IContactSearchItem) => void;
21
+ }
22
+
23
+ const FILTER_ITEMS: KTagsChoiceSlideBarItemProps[] = [
24
+ {
25
+ id: ContactCategory.Customer,
26
+ label: trans('contact_type:customer'),
27
+ allowDeselect: true,
28
+ },
29
+ {
30
+ id: ContactCategory.Pdp,
31
+ label: trans('contact_type:pdp'),
32
+ allowDeselect: true,
33
+ },
34
+ {
35
+ id: ContactCategory.Group,
36
+ label: trans('contact_type:group'),
37
+ allowDeselect: true,
38
+ },
39
+ {
40
+ id: ContactCategory.Seller,
41
+ label: trans('contact_type:seller'),
42
+ allowDeselect: true,
43
+ },
44
+ ];
45
+
46
+ const SearchContactsResult = memo(
47
+ ({ searchTerm, onPressSearchContact }: ISearchContactsResultProps) => {
48
+ const { applicationType } = useChatContext();
49
+ const [contactType, setContactType] = useState<
50
+ ContactCategory | undefined
51
+ >();
52
+
53
+ const {
54
+ dataFlatten,
55
+ hasNextPage,
56
+ fetchNextPage,
57
+ isFetchingNextPage,
58
+ isFetching,
59
+ isRefetching,
60
+ refetch,
61
+ } = useSearchContacts({
62
+ query: searchTerm,
63
+ applicationType,
64
+ type: contactType,
65
+ });
66
+
67
+ const onFilterChanged = useCallback(
68
+ (item?: KTagsChoiceSlideBarItemProps) => {
69
+ setContactType(item?.id as ContactCategory | undefined);
70
+ },
71
+ []
72
+ );
73
+
74
+ const renderItem = useCallback(
75
+ ({ item }: { item: IContactSearchItem }) => {
76
+ return (
77
+ <ContactResultItem
78
+ item={item}
79
+ onPressSearchContact={onPressSearchContact}
80
+ searchTerm={searchTerm}
81
+ />
82
+ );
83
+ },
84
+ [onPressSearchContact, searchTerm]
85
+ );
86
+
87
+ const keyExtractor = useCallback(
88
+ (item: IContactSearchItem) => item?.id,
89
+ []
90
+ );
91
+
92
+ const onEndReached = useCallback(() => {
93
+ if (hasNextPage && !isFetchingNextPage) {
94
+ fetchNextPage();
95
+ }
96
+ }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
97
+
98
+ const ListHeaderComponent = useMemo(
99
+ () => (
100
+ <KContainer.View style={styles.filterContainer}>
101
+ <KTagsChoiceSlideBar
102
+ items={FILTER_ITEMS}
103
+ onChanged={onFilterChanged}
104
+ containerStyle={styles.filterContentContainer}
105
+ type="outline"
106
+ />
107
+ </KContainer.View>
108
+ ),
109
+ [onFilterChanged]
110
+ );
111
+
112
+ return (
113
+ <KContainer.View flex>
114
+ {ListHeaderComponent}
115
+ {isFetching && !isFetchingNextPage && !isRefetching ? (
116
+ <KContainer.View flex center>
117
+ <ActivityIndicator color={KColors.primary.normal} size={'small'} />
118
+ </KContainer.View>
119
+ ) : (
120
+ <FlatList
121
+ data={dataFlatten}
122
+ renderItem={renderItem}
123
+ keyExtractor={keyExtractor}
124
+ contentContainerStyle={styles.container}
125
+ showsVerticalScrollIndicator={false}
126
+ onEndReached={onEndReached}
127
+ ListEmptyComponent={
128
+ <SearchEmpty
129
+ searchTerm={searchTerm}
130
+ searchType={SearchConversationTabResultType.Conversations}
131
+ />
132
+ }
133
+ refreshing={isFetching}
134
+ onRefresh={refetch}
135
+ />
136
+ )}
137
+ </KContainer.View>
138
+ );
139
+ }
140
+ );
141
+
142
+ export default SearchContactsResult;
143
+
144
+ const styles = StyleSheet.create({
145
+ container: {
146
+ paddingHorizontal: KSpacingValue['0.75rem'],
147
+ },
148
+ filterContainer: {
149
+ borderBottomWidth: 1,
150
+ borderBottomColor: KColors.border.light,
151
+ },
152
+ filterContentContainer: {
153
+ padding: 12,
154
+ },
155
+ });
@@ -22,6 +22,8 @@ export const SearchEmpty = memo(
22
22
  return 'search_message:empty_hint';
23
23
  case SearchConversationTabResultType.Conversations:
24
24
  return 'search_conversation:empty_hint';
25
+ case SearchConversationTabResultType.Community:
26
+ return 'search_community:empty_hint';
25
27
  default:
26
28
  return 'search:empty_hint';
27
29
  }