@droppii-org/chat-mobile 0.2.57 → 0.2.59

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 (128) 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/usePinMessage.js +30 -26
  15. package/lib/module/hooks/message/usePinMessage.js.map +1 -1
  16. package/lib/module/hooks/message/useSearchMessage.js +58 -0
  17. package/lib/module/hooks/message/useSearchMessage.js.map +1 -0
  18. package/lib/module/hooks/query-keys.js +4 -0
  19. package/lib/module/hooks/query-keys.js.map +1 -1
  20. package/lib/module/index.js +1 -0
  21. package/lib/module/index.js.map +1 -1
  22. package/lib/module/screens/ChatInfo/ChatInfoActions.js +2 -2
  23. package/lib/module/screens/ChatInfo/ChatInfoActions.js.map +1 -1
  24. package/lib/module/screens/ChatInfo/ChatInfoMediaRow.js +1 -1
  25. package/lib/module/screens/ChatInfo/ChatInfoMediaRow.js.map +1 -1
  26. package/lib/module/screens/SearchConversation/SearchAllResult.js +123 -0
  27. package/lib/module/screens/SearchConversation/SearchAllResult.js.map +1 -0
  28. package/lib/module/screens/SearchConversation/SearchEmpty.js +32 -0
  29. package/lib/module/screens/SearchConversation/SearchEmpty.js.map +1 -0
  30. package/lib/module/screens/SearchConversation/SearchMessagesResult.js +71 -0
  31. package/lib/module/screens/SearchConversation/SearchMessagesResult.js.map +1 -0
  32. package/lib/module/screens/SearchConversation/SearchResultItem.js +102 -0
  33. package/lib/module/screens/SearchConversation/SearchResultItem.js.map +1 -0
  34. package/lib/module/screens/SearchConversation/TabResult.js +118 -0
  35. package/lib/module/screens/SearchConversation/TabResult.js.map +1 -0
  36. package/lib/module/screens/SearchConversation/index.js +57 -0
  37. package/lib/module/screens/SearchConversation/index.js.map +1 -0
  38. package/lib/module/screens/SearchConversation/types.js +9 -0
  39. package/lib/module/screens/SearchConversation/types.js.map +1 -0
  40. package/lib/module/screens/chat-detail/BotMenuSheet.js +4 -4
  41. package/lib/module/screens/chat-detail/BotMenuSheet.js.map +1 -1
  42. package/lib/module/screens/chat-detail/ChatComposer.js +19 -4
  43. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  44. package/lib/module/screens/chat-detail/ChatDetail.js +3 -1
  45. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  46. package/lib/module/services/endpoints.js +2 -1
  47. package/lib/module/services/endpoints.js.map +1 -1
  48. package/lib/module/translation/resources/i18n.js +7 -1
  49. package/lib/module/translation/resources/i18n.js.map +1 -1
  50. package/lib/module/types/chat.js.map +1 -1
  51. package/lib/module/utils/messageUtils.js +5 -5
  52. package/lib/module/utils/messageUtils.js.map +1 -1
  53. package/lib/typescript/src/assets/images/index.d.ts +1 -0
  54. package/lib/typescript/src/assets/images/index.d.ts.map +1 -1
  55. package/lib/typescript/src/components/Avatar/Avatar.types.d.ts +1 -1
  56. package/lib/typescript/src/components/Avatar/Avatar.types.d.ts.map +1 -1
  57. package/lib/typescript/src/components/Avatar/Avatar.utils.d.ts +2 -0
  58. package/lib/typescript/src/components/Avatar/Avatar.utils.d.ts.map +1 -1
  59. package/lib/typescript/src/components/Avatar/SingleAvatar.d.ts.map +1 -1
  60. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +2 -1
  61. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
  62. package/lib/typescript/src/config/api-endpoints.d.ts +1 -0
  63. package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
  64. package/lib/typescript/src/constants/index.d.ts +2 -0
  65. package/lib/typescript/src/constants/index.d.ts.map +1 -0
  66. package/lib/typescript/src/hooks/message/usePinMessage.d.ts.map +1 -1
  67. package/lib/typescript/src/hooks/message/useSearchMessage.d.ts +221 -0
  68. package/lib/typescript/src/hooks/message/useSearchMessage.d.ts.map +1 -0
  69. package/lib/typescript/src/hooks/query-keys.d.ts +4 -0
  70. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  71. package/lib/typescript/src/index.d.ts +1 -0
  72. package/lib/typescript/src/index.d.ts.map +1 -1
  73. package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts +10 -0
  74. package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts.map +1 -0
  75. package/lib/typescript/src/screens/SearchConversation/SearchEmpty.d.ts +2 -0
  76. package/lib/typescript/src/screens/SearchConversation/SearchEmpty.d.ts.map +1 -0
  77. package/lib/typescript/src/screens/SearchConversation/SearchMessagesResult.d.ts +8 -0
  78. package/lib/typescript/src/screens/SearchConversation/SearchMessagesResult.d.ts.map +1 -0
  79. package/lib/typescript/src/screens/SearchConversation/SearchResultItem.d.ts +10 -0
  80. package/lib/typescript/src/screens/SearchConversation/SearchResultItem.d.ts.map +1 -0
  81. package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts +8 -0
  82. package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts.map +1 -0
  83. package/lib/typescript/src/screens/SearchConversation/index.d.ts +7 -0
  84. package/lib/typescript/src/screens/SearchConversation/index.d.ts.map +1 -0
  85. package/lib/typescript/src/screens/SearchConversation/types.d.ts +6 -0
  86. package/lib/typescript/src/screens/SearchConversation/types.d.ts.map +1 -0
  87. package/lib/typescript/src/screens/chat-detail/BotMenuSheet.d.ts.map +1 -1
  88. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  89. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
  90. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  91. package/lib/typescript/src/screens/chat-detail/types.d.ts +1 -0
  92. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  93. package/lib/typescript/src/services/endpoints.d.ts +1 -0
  94. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  95. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  96. package/lib/typescript/src/types/chat.d.ts +13 -1
  97. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  98. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  99. package/package.json +1 -1
  100. package/src/assets/images/index.ts +1 -0
  101. package/src/assets/images/search_empty.png +0 -0
  102. package/src/components/Avatar/Avatar.types.ts +1 -1
  103. package/src/components/Avatar/Avatar.utils.ts +5 -3
  104. package/src/components/Avatar/SingleAvatar.tsx +2 -0
  105. package/src/components/ThreadCard/AvatarSection.tsx +5 -2
  106. package/src/config/api-endpoints.ts +2 -0
  107. package/src/constants/index.ts +1 -0
  108. package/src/hooks/message/usePinMessage.ts +34 -30
  109. package/src/hooks/message/useSearchMessage.ts +78 -0
  110. package/src/hooks/query-keys.ts +6 -0
  111. package/src/index.tsx +1 -0
  112. package/src/screens/ChatInfo/ChatInfoActions.tsx +2 -2
  113. package/src/screens/ChatInfo/ChatInfoMediaRow.tsx +1 -1
  114. package/src/screens/SearchConversation/SearchAllResult.tsx +154 -0
  115. package/src/screens/SearchConversation/SearchEmpty.tsx +30 -0
  116. package/src/screens/SearchConversation/SearchMessagesResult.tsx +74 -0
  117. package/src/screens/SearchConversation/SearchResultItem.tsx +107 -0
  118. package/src/screens/SearchConversation/TabResult.tsx +144 -0
  119. package/src/screens/SearchConversation/index.tsx +61 -0
  120. package/src/screens/SearchConversation/types.tsx +5 -0
  121. package/src/screens/chat-detail/BotMenuSheet.tsx +2 -3
  122. package/src/screens/chat-detail/ChatComposer.tsx +27 -8
  123. package/src/screens/chat-detail/ChatDetail.tsx +2 -1
  124. package/src/screens/chat-detail/types.ts +1 -0
  125. package/src/services/endpoints.ts +1 -0
  126. package/src/translation/resources/i18n.ts +7 -0
  127. package/src/types/chat.ts +15 -0
  128. package/src/utils/messageUtils.ts +13 -6
@@ -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;
@@ -0,0 +1,5 @@
1
+ export enum SearchConversationTabResultType {
2
+ All = 'all',
3
+ Conversations = 'conversations',
4
+ Messages = 'messages',
5
+ }
@@ -31,9 +31,8 @@ const BotMenuSheetContent = memo(
31
31
  return (
32
32
  <KContainer.View paddingH="0.75rem" paddingV="0.25rem">
33
33
  {options?.map((item, index) => (
34
- <>
34
+ <KContainer.View key={item.id}>
35
35
  <KContainer.Touchable
36
- key={item.id}
37
36
  row
38
37
  alignItems
39
38
  justifyContent="space-between"
@@ -54,7 +53,7 @@ const BotMenuSheetContent = memo(
54
53
  <KContainer.VisibleView visible={index !== options?.length - 1}>
55
54
  <KDivider size="hairline" />
56
55
  </KContainer.VisibleView>
57
- </>
56
+ </KContainer.View>
58
57
  ))}
59
58
  </KContainer.View>
60
59
  );
@@ -48,9 +48,13 @@ import { useChatComposerState } from './hooks/useChatComposerState';
48
48
  import { useChatComposerAnimation } from './hooks/useChatComposerAnimation';
49
49
  import { useChatDetailContext } from '../../context/ChatDetailContext';
50
50
  import { trans } from '../../translation';
51
- import { useCloseSupportSession, useRunFlow } from '../../hooks/flows/useFlow';
51
+ import {
52
+ useCloseSupportSession,
53
+ useResetFlow,
54
+ useRunFlow,
55
+ } from '../../hooks/flows/useFlow';
52
56
  import { ReactFlowsNodeTypes, type InputButtonItem } from '../../types/flows';
53
- import { isContextButton } from '../../utils/flows';
57
+ import { DEFAULT_BUTTON_ID, isContextButton } from '../../utils/flows';
54
58
  import { DFlowState } from '../../types/chat';
55
59
  import { useAttachmentMapping } from './hooks/useAttachmentMapping';
56
60
  import type { AttachmentType } from '../../types/attachment';
@@ -87,6 +91,7 @@ export const ChatComposer = memo(
87
91
  flowState,
88
92
  } = useChatDetailContext();
89
93
  const { mutate: runFlow, isPending } = useRunFlow();
94
+ const { mutate: resetFlow } = useResetFlow();
90
95
  const { mutateAsync: closeSupportSession } = useCloseSupportSession();
91
96
  const { sendMessage } = useSendMessage();
92
97
  const compose = useChatCompose();
@@ -215,18 +220,32 @@ export const ChatComposer = memo(
215
220
  });
216
221
  }, [runFlow, conversationFlow]);
217
222
 
218
- const botMenuOptions = useMemo(
219
- () =>
223
+ const botMenuOptions = useMemo(() => {
224
+ const apiOptions =
220
225
  conversationFlowMainMenu?.filter(
221
226
  (item) => !isContextButton(item.label)
222
- ) || [],
223
- [conversationFlowMainMenu]
224
- );
227
+ ) || [];
228
+
229
+ const hasDefaultButton = apiOptions.some(
230
+ (item) => item.id === DEFAULT_BUTTON_ID
231
+ );
232
+
233
+ if (hasDefaultButton) return apiOptions;
234
+
235
+ return [
236
+ ...apiOptions,
237
+ { id: DEFAULT_BUTTON_ID, label: trans('restart') },
238
+ ];
239
+ }, [conversationFlowMainMenu]);
225
240
 
226
241
  const hasBotMenuOptions = isBotConversation && botMenuOptions.length > 0;
227
242
 
228
243
  const runBotMenuOption = useCallback(
229
244
  (item: InputButtonItem) => {
245
+ if (item.id === DEFAULT_BUTTON_ID) {
246
+ resetFlow();
247
+ return;
248
+ }
230
249
  runFlow({
231
250
  actionId: item.id,
232
251
  groupId: '',
@@ -234,7 +253,7 @@ export const ChatComposer = memo(
234
253
  plainText: item.label,
235
254
  });
236
255
  },
237
- [runFlow]
256
+ [runFlow, resetFlow]
238
257
  );
239
258
 
240
259
  const handleSelectBotMenuOption = useCallback(
@@ -41,6 +41,7 @@ const ChatDetail = memo(
41
41
  targetUserId = '',
42
42
  targetGroupId = '',
43
43
  targetBotId = '',
44
+ targetMessageId = '',
44
45
  showAddMember,
45
46
  onBack,
46
47
  onPressSearch,
@@ -91,7 +92,7 @@ const ChatDetail = memo(
91
92
  getMoreNewMessages,
92
93
  hasMoreOld,
93
94
  hasMoreNew,
94
- } = useMessages({ conversationId });
95
+ } = useMessages({ conversationId, startClientMsgId: targetMessageId });
95
96
 
96
97
  const setShowFLashMessage = useSetIsShowFLashMessage();
97
98
 
@@ -94,6 +94,7 @@ export interface ChatDetailProps extends Omit<
94
94
  targetUserId?: string;
95
95
  targetGroupId?: string;
96
96
  targetBotId?: string;
97
+ targetMessageId?: string;
97
98
  enabled?: boolean;
98
99
  showAddMember?: boolean;
99
100
  onPressUrl?: (url: string) => void;