@droppii-org/chat-mobile 0.2.49 → 0.2.51

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 (159) hide show
  1. package/lib/module/components/StickerPicker/StickerCategoryTabs.js +86 -0
  2. package/lib/module/components/StickerPicker/StickerCategoryTabs.js.map +1 -0
  3. package/lib/module/components/StickerPicker/StickerPickerPlaceholder.js +18 -0
  4. package/lib/module/components/StickerPicker/StickerPickerPlaceholder.js.map +1 -0
  5. package/lib/module/components/StickerPicker/StickerPickerRowItem.js +35 -0
  6. package/lib/module/components/StickerPicker/StickerPickerRowItem.js.map +1 -0
  7. package/lib/module/components/StickerPicker/StickerSectionList.js +110 -0
  8. package/lib/module/components/StickerPicker/StickerSectionList.js.map +1 -0
  9. package/lib/module/components/StickerPicker/constants.js +14 -0
  10. package/lib/module/components/StickerPicker/constants.js.map +1 -0
  11. package/lib/module/components/StickerPicker/index.js +134 -0
  12. package/lib/module/components/StickerPicker/index.js.map +1 -0
  13. package/lib/module/components/ThreadCard/MuteThread.js +4 -28
  14. package/lib/module/components/ThreadCard/MuteThread.js.map +1 -1
  15. package/lib/module/components/ThreadCard/PinThread.js +45 -0
  16. package/lib/module/components/ThreadCard/PinThread.js.map +1 -0
  17. package/lib/module/components/ThreadCard/ThreadCard.js +14 -9
  18. package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
  19. package/lib/module/components/messages/stickerMessage/index.js +28 -0
  20. package/lib/module/components/messages/stickerMessage/index.js.map +1 -0
  21. package/lib/module/config/api-endpoints.js +4 -1
  22. package/lib/module/config/api-endpoints.js.map +1 -1
  23. package/lib/module/hooks/conversation/useGetConversationDetail.js +15 -0
  24. package/lib/module/hooks/conversation/useGetConversationDetail.js.map +1 -1
  25. package/lib/module/hooks/flows/useGetConversationFlowMainMenu.js +1 -1
  26. package/lib/module/hooks/flows/useGetConversationFlowMainMenu.js.map +1 -1
  27. package/lib/module/hooks/message/useSendMessage.js +45 -18
  28. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  29. package/lib/module/hooks/query-keys.js +7 -0
  30. package/lib/module/hooks/query-keys.js.map +1 -1
  31. package/lib/module/hooks/stickers/useFetchStickerByCategory.js +12 -0
  32. package/lib/module/hooks/stickers/useFetchStickerByCategory.js.map +1 -0
  33. package/lib/module/hooks/stickers/useStickerCategories.js +14 -0
  34. package/lib/module/hooks/stickers/useStickerCategories.js.map +1 -0
  35. package/lib/module/hooks/stickers/useStickerHistory.js +16 -0
  36. package/lib/module/hooks/stickers/useStickerHistory.js.map +1 -0
  37. package/lib/module/hooks/useChatCompose.js +1 -18
  38. package/lib/module/hooks/useChatCompose.js.map +1 -1
  39. package/lib/module/hooks/useMuteConversation.js +34 -15
  40. package/lib/module/hooks/useMuteConversation.js.map +1 -1
  41. package/lib/module/hooks/usePinConversation.js +72 -0
  42. package/lib/module/hooks/usePinConversation.js.map +1 -0
  43. package/lib/module/screens/chat-detail/ChatComposer.js +64 -16
  44. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  45. package/lib/module/screens/chat-detail/ChatDetail.js +6 -20
  46. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  47. package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js +15 -4
  48. package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js.map +1 -1
  49. package/lib/module/screens/chat-detail/hooks/useChatComposerState.js +3 -0
  50. package/lib/module/screens/chat-detail/hooks/useChatComposerState.js.map +1 -1
  51. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +5 -6
  52. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  53. package/lib/module/services/attachmentOrchestrator.js +1 -1
  54. package/lib/module/services/attachmentOrchestrator.js.map +1 -1
  55. package/lib/module/services/endpoints.js +4 -1
  56. package/lib/module/services/endpoints.js.map +1 -1
  57. package/lib/module/store/conversation.js +1 -20
  58. package/lib/module/store/conversation.js.map +1 -1
  59. package/lib/module/translation/resources/i18n.js +10 -1
  60. package/lib/module/translation/resources/i18n.js.map +1 -1
  61. package/lib/module/types/chat.js +6 -0
  62. package/lib/module/types/chat.js.map +1 -1
  63. package/lib/module/types/sticker.js +2 -0
  64. package/lib/module/types/sticker.js.map +1 -0
  65. package/lib/module/utils/conversation.js +1 -24
  66. package/lib/module/utils/conversation.js.map +1 -1
  67. package/lib/module/utils/messageUtils.js +2 -0
  68. package/lib/module/utils/messageUtils.js.map +1 -1
  69. package/lib/typescript/src/components/StickerPicker/StickerCategoryTabs.d.ts +9 -0
  70. package/lib/typescript/src/components/StickerPicker/StickerCategoryTabs.d.ts.map +1 -0
  71. package/lib/typescript/src/components/StickerPicker/StickerPickerPlaceholder.d.ts +2 -0
  72. package/lib/typescript/src/components/StickerPicker/StickerPickerPlaceholder.d.ts.map +1 -0
  73. package/lib/typescript/src/components/StickerPicker/StickerPickerRowItem.d.ts +8 -0
  74. package/lib/typescript/src/components/StickerPicker/StickerPickerRowItem.d.ts.map +1 -0
  75. package/lib/typescript/src/components/StickerPicker/StickerSectionList.d.ts +15 -0
  76. package/lib/typescript/src/components/StickerPicker/StickerSectionList.d.ts.map +1 -0
  77. package/lib/typescript/src/components/StickerPicker/constants.d.ts +11 -0
  78. package/lib/typescript/src/components/StickerPicker/constants.d.ts.map +1 -0
  79. package/lib/typescript/src/components/StickerPicker/index.d.ts +7 -0
  80. package/lib/typescript/src/components/StickerPicker/index.d.ts.map +1 -0
  81. package/lib/typescript/src/components/ThreadCard/MuteThread.d.ts.map +1 -1
  82. package/lib/typescript/src/components/ThreadCard/PinThread.d.ts +10 -0
  83. package/lib/typescript/src/components/ThreadCard/PinThread.d.ts.map +1 -0
  84. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  85. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts +3 -0
  86. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts.map +1 -0
  87. package/lib/typescript/src/config/api-endpoints.d.ts +3 -0
  88. package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
  89. package/lib/typescript/src/hooks/conversation/useGetConversationDetail.d.ts +2 -1
  90. package/lib/typescript/src/hooks/conversation/useGetConversationDetail.d.ts.map +1 -1
  91. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +5 -3
  92. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  93. package/lib/typescript/src/hooks/query-keys.d.ts +7 -0
  94. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  95. package/lib/typescript/src/hooks/stickers/useFetchStickerByCategory.d.ts +3 -0
  96. package/lib/typescript/src/hooks/stickers/useFetchStickerByCategory.d.ts.map +1 -0
  97. package/lib/typescript/src/hooks/stickers/useStickerCategories.d.ts +3 -0
  98. package/lib/typescript/src/hooks/stickers/useStickerCategories.d.ts.map +1 -0
  99. package/lib/typescript/src/hooks/stickers/useStickerHistory.d.ts +3 -0
  100. package/lib/typescript/src/hooks/stickers/useStickerHistory.d.ts.map +1 -0
  101. package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -1
  102. package/lib/typescript/src/hooks/useMuteConversation.d.ts.map +1 -1
  103. package/lib/typescript/src/hooks/usePinConversation.d.ts +6 -0
  104. package/lib/typescript/src/hooks/usePinConversation.d.ts.map +1 -0
  105. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +2 -2
  106. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  107. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  108. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts +2 -1
  109. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts.map +1 -1
  110. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts +3 -0
  111. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts.map +1 -1
  112. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  113. package/lib/typescript/src/services/endpoints.d.ts +4 -1
  114. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  115. package/lib/typescript/src/store/conversation.d.ts +0 -3
  116. package/lib/typescript/src/store/conversation.d.ts.map +1 -1
  117. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  118. package/lib/typescript/src/types/chat.d.ts +12 -19
  119. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  120. package/lib/typescript/src/types/sticker.d.ts +21 -0
  121. package/lib/typescript/src/types/sticker.d.ts.map +1 -0
  122. package/lib/typescript/src/utils/conversation.d.ts +1 -2
  123. package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
  124. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  125. package/package.json +2 -2
  126. package/src/components/StickerPicker/StickerCategoryTabs.tsx +103 -0
  127. package/src/components/StickerPicker/StickerPickerPlaceholder.tsx +16 -0
  128. package/src/components/StickerPicker/StickerPickerRowItem.tsx +43 -0
  129. package/src/components/StickerPicker/StickerSectionList.tsx +141 -0
  130. package/src/components/StickerPicker/constants.ts +20 -0
  131. package/src/components/StickerPicker/index.tsx +166 -0
  132. package/src/components/ThreadCard/MuteThread.tsx +4 -28
  133. package/src/components/ThreadCard/PinThread.tsx +58 -0
  134. package/src/components/ThreadCard/ThreadCard.tsx +11 -5
  135. package/src/components/messages/stickerMessage/index.tsx +26 -0
  136. package/src/config/api-endpoints.ts +6 -0
  137. package/src/hooks/conversation/useGetConversationDetail.ts +20 -1
  138. package/src/hooks/flows/useGetConversationFlowMainMenu.ts +1 -1
  139. package/src/hooks/message/useSendMessage.ts +49 -27
  140. package/src/hooks/query-keys.ts +10 -0
  141. package/src/hooks/stickers/useFetchStickerByCategory.ts +15 -0
  142. package/src/hooks/stickers/useStickerCategories.ts +17 -0
  143. package/src/hooks/stickers/useStickerHistory.ts +19 -0
  144. package/src/hooks/useChatCompose.ts +0 -19
  145. package/src/hooks/useMuteConversation.ts +32 -17
  146. package/src/hooks/usePinConversation.ts +87 -0
  147. package/src/screens/chat-detail/ChatComposer.tsx +106 -19
  148. package/src/screens/chat-detail/ChatDetail.tsx +5 -19
  149. package/src/screens/chat-detail/hooks/useChatComposerAnimation.ts +21 -5
  150. package/src/screens/chat-detail/hooks/useChatComposerState.ts +7 -0
  151. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +4 -5
  152. package/src/services/attachmentOrchestrator.ts +1 -1
  153. package/src/services/endpoints.ts +6 -1
  154. package/src/store/conversation.ts +0 -24
  155. package/src/translation/resources/i18n.ts +11 -0
  156. package/src/types/chat.ts +13 -21
  157. package/src/types/sticker.ts +23 -0
  158. package/src/utils/conversation.ts +0 -28
  159. package/src/utils/messageUtils.ts +3 -0
@@ -0,0 +1,166 @@
1
+ import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
2
+ import { KColors, KContainer } from '@droppii/libs';
3
+ import type {
4
+ StickerCategory,
5
+ StickerItem,
6
+ StickerRow,
7
+ } from '../../types/sticker';
8
+ import { useStickerCategories } from '../../hooks/stickers/useStickerCategories';
9
+ import { useStickerHistory } from '../../hooks/stickers/useStickerHistory';
10
+ import { useFetchStickerByCategory } from '../../hooks/stickers/useFetchStickerByCategory';
11
+ import {
12
+ RECENT_CATEGORY_ID,
13
+ STICKER_COLUMNS,
14
+ STICKER_PICKER_HEIGHT,
15
+ } from './constants';
16
+ import { StickerCategoryTabs } from './StickerCategoryTabs';
17
+ import {
18
+ StickerSectionList,
19
+ type StickerPickerSection,
20
+ } from './StickerSectionList';
21
+ import { trans } from '../../translation';
22
+
23
+ interface Props {
24
+ onSelectSticker: (sticker: StickerItem) => void;
25
+ }
26
+
27
+ const chunkIntoRows = (items: StickerItem[], columns: number): StickerRow[] => {
28
+ const rows: StickerRow[] = [];
29
+ for (let i = 0; i < items.length; i += columns) {
30
+ rows.push(items.slice(i, i + columns));
31
+ }
32
+ return rows;
33
+ };
34
+
35
+ const makePlaceholderRows = (count: number, columns: number): StickerRow[] => {
36
+ const rows: StickerRow[] = [];
37
+ for (let i = 0; i < count; i += columns) {
38
+ const rowSize = Math.min(columns, count - i);
39
+ rows.push(Array.from({ length: rowSize }, () => null));
40
+ }
41
+ return rows;
42
+ };
43
+
44
+ export const StickerPicker = ({ onSelectSticker }: Props) => {
45
+ const { data: categoriesData } = useStickerCategories();
46
+ const { data: recentStickers } = useStickerHistory();
47
+ const { mutateAsync: fetchStickersByCategory } = useFetchStickerByCategory();
48
+
49
+ const allCategories = useMemo<StickerCategory[]>(() => {
50
+ const recent: StickerCategory = {
51
+ id: RECENT_CATEGORY_ID,
52
+ name: trans('recent'),
53
+ icon: null,
54
+ stickerCount: 0,
55
+ };
56
+ return [recent, ...(categoriesData || [])];
57
+ }, [categoriesData]);
58
+
59
+ const [activeCategory, setActiveCategory] = useState(RECENT_CATEGORY_ID);
60
+ const [sectionDataMap, setSectionDataMap] = useState<
61
+ Record<string, StickerRow[]>
62
+ >({});
63
+
64
+ const loadedIdsRef = useRef(new Set<string>([RECENT_CATEGORY_ID]));
65
+ const sectionListRef = useRef<any>(null);
66
+ const suppressCategoryChangeRef = useRef(false);
67
+ const suppressTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
68
+
69
+ // Populate recent section when history data arrives
70
+ useEffect(() => {
71
+ if (recentStickers) {
72
+ setSectionDataMap((prev) => ({
73
+ ...prev,
74
+ [RECENT_CATEGORY_ID]: chunkIntoRows(recentStickers, STICKER_COLUMNS),
75
+ }));
76
+ }
77
+ }, [recentStickers]);
78
+
79
+ // Cleanup suppress timer on unmount
80
+ useEffect(() => {
81
+ return () => {
82
+ if (suppressTimerRef.current) clearTimeout(suppressTimerRef.current);
83
+ };
84
+ }, []);
85
+
86
+ const sections = useMemo<StickerPickerSection[]>(
87
+ () =>
88
+ allCategories.map((cat) => ({
89
+ categoryId: cat.id,
90
+ title: cat.name,
91
+ data:
92
+ sectionDataMap[cat.id] ??
93
+ makePlaceholderRows(cat.stickerCount, STICKER_COLUMNS),
94
+ })),
95
+ [allCategories, sectionDataMap]
96
+ );
97
+
98
+ const handleSectionVisible = useCallback(
99
+ async (categoryId: string) => {
100
+ if (categoryId === RECENT_CATEGORY_ID) return;
101
+ if (loadedIdsRef.current.has(categoryId)) return;
102
+ loadedIdsRef.current.add(categoryId);
103
+
104
+ try {
105
+ const stickers = await fetchStickersByCategory(categoryId);
106
+ setSectionDataMap((prev) => ({
107
+ ...prev,
108
+ [categoryId]: chunkIntoRows(stickers, STICKER_COLUMNS),
109
+ }));
110
+ } catch {
111
+ loadedIdsRef.current.delete(categoryId);
112
+ }
113
+ },
114
+ [fetchStickersByCategory]
115
+ );
116
+
117
+ const handleActiveCategoryChange = useCallback((categoryId: string) => {
118
+ if (suppressCategoryChangeRef.current) return;
119
+ setActiveCategory(categoryId);
120
+ }, []);
121
+
122
+ const handleCategoryPress = useCallback(
123
+ (categoryId: string) => {
124
+ setActiveCategory(categoryId);
125
+
126
+ suppressCategoryChangeRef.current = true;
127
+ if (suppressTimerRef.current) clearTimeout(suppressTimerRef.current);
128
+ suppressTimerRef.current = setTimeout(() => {
129
+ suppressCategoryChangeRef.current = false;
130
+ }, 600);
131
+
132
+ const sectionIndex = allCategories.findIndex((c) => c.id === categoryId);
133
+ if (sectionIndex >= 0 && sectionListRef.current) {
134
+ try {
135
+ sectionListRef.current.scrollToLocation({
136
+ sectionIndex,
137
+ itemIndex: 0,
138
+ animated: true,
139
+ viewOffset: 0,
140
+ });
141
+ } catch {}
142
+ }
143
+ },
144
+ [allCategories]
145
+ );
146
+
147
+ return (
148
+ <KContainer.View
149
+ style={{ height: STICKER_PICKER_HEIGHT }}
150
+ background={KColors.white}
151
+ >
152
+ <StickerCategoryTabs
153
+ categories={allCategories}
154
+ activeCategory={activeCategory}
155
+ onSelectCategory={handleCategoryPress}
156
+ />
157
+ <StickerSectionList
158
+ ref={sectionListRef}
159
+ sections={sections}
160
+ onSectionVisible={handleSectionVisible}
161
+ onActiveCategoryChange={handleActiveCategoryChange}
162
+ onSelectSticker={onSelectSticker}
163
+ />
164
+ </KContainer.View>
165
+ );
166
+ };
@@ -58,20 +58,8 @@ export const MuteThread = memo(
58
58
  label: trans('chat:mute_confirm_primary'),
59
59
  kind: 'primary',
60
60
  onPress: async () => {
61
- try {
62
- await muteConversation(conversation);
63
- UIUtils.toast.open({
64
- title: trans('chat:mute_toast_success'),
65
- theme: 'light',
66
- });
67
- } catch {
68
- UIUtils.toast.open({
69
- title: trans('chat:generic_error'),
70
- theme: 'error',
71
- });
72
- } finally {
73
- swipeableMethods.close();
74
- }
61
+ swipeableMethods.close();
62
+ await muteConversation(conversation);
75
63
  },
76
64
  },
77
65
  ],
@@ -81,20 +69,8 @@ export const MuteThread = memo(
81
69
  }, [swipeableMethods, conversation, muteConversation]);
82
70
 
83
71
  const handleUnmutePress = useCallback(async () => {
84
- try {
85
- await unmuteConversation(conversation);
86
- UIUtils.toast.open({
87
- title: trans('chat:unmute_toast_success'),
88
- theme: 'light',
89
- });
90
- } catch {
91
- UIUtils.toast.open({
92
- title: trans('chat:generic_error'),
93
- theme: 'error',
94
- });
95
- } finally {
96
- swipeableMethods.close();
97
- }
72
+ swipeableMethods.close();
73
+ await unmuteConversation(conversation);
98
74
  }, [swipeableMethods, conversation, unmuteConversation]);
99
75
 
100
76
  return (
@@ -0,0 +1,58 @@
1
+ import { memo, useCallback } from 'react';
2
+ import type { SwipeableMethods } from 'react-native-gesture-handler/ReanimatedSwipeable';
3
+ import { KContainer, KImage, KLabel, KColors } from '@droppii/libs';
4
+ import { usePinConversation } from '../../hooks/usePinConversation';
5
+ import { trans } from '../../translation';
6
+ import { type ConversationItem } from '@droppii/openim-rn-client-sdk';
7
+
8
+ interface PinThreadProps {
9
+ isPinned: boolean;
10
+ conversation?: ConversationItem;
11
+ swipeableMethods: SwipeableMethods;
12
+ }
13
+
14
+ export const PinThread = memo(
15
+ ({ isPinned, conversation, swipeableMethods }: PinThreadProps) => {
16
+ const { pinConversation, unpinConversation } = usePinConversation();
17
+
18
+ const handlePress = useCallback(async () => {
19
+ swipeableMethods.close();
20
+ if (isPinned) {
21
+ await unpinConversation(conversation);
22
+ } else {
23
+ await pinConversation(conversation);
24
+ }
25
+ }, [
26
+ isPinned,
27
+ conversation,
28
+ pinConversation,
29
+ unpinConversation,
30
+ swipeableMethods,
31
+ ]);
32
+
33
+ return (
34
+ <KContainer.Touchable
35
+ width={76}
36
+ alignItems="center"
37
+ justifyContent="center"
38
+ background={KColors.palette.primary.w400}
39
+ onPress={handlePress}
40
+ >
41
+ <KImage.VectorIcons
42
+ name={isPinned ? 'thumbtack-off-b' : 'thumbtack-b'}
43
+ provider="DroppiiNew"
44
+ size={16}
45
+ color={KColors.white}
46
+ />
47
+ <KLabel.Text
48
+ typo="Text2XsMedium"
49
+ color={KColors.white}
50
+ marginT="0.25rem"
51
+ textAlign="center"
52
+ >
53
+ {isPinned ? trans('chat:unpin_thread') : trans('chat:pin_thread')}
54
+ </KLabel.Text>
55
+ </KContainer.Touchable>
56
+ );
57
+ }
58
+ );
@@ -17,6 +17,7 @@ import {
17
17
  type MessageItem,
18
18
  } from '@droppii/openim-rn-client-sdk';
19
19
  import { MuteThread } from './MuteThread';
20
+ import { PinThread } from './PinThread';
20
21
  import { useIsJoinedGroup } from '../../hooks/useIsJoinedGroup';
21
22
  import isEmpty from 'lodash/isEmpty';
22
23
 
@@ -84,6 +85,11 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
84
85
  ) => (
85
86
  <>
86
87
  <KContainer.VisibleView visible={!isNotInGroup}>
88
+ <PinThread
89
+ conversation={conversation}
90
+ isPinned={Boolean(isPinned)}
91
+ swipeableMethods={swipeableMethods}
92
+ />
87
93
  <MuteThread
88
94
  conversation={conversation}
89
95
  isMuted={isMuted}
@@ -92,7 +98,7 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
92
98
  </KContainer.VisibleView>
93
99
  </>
94
100
  ),
95
- [isMuted, conversation, isNotInGroup]
101
+ [isMuted, conversation, isNotInGroup, isPinned]
96
102
  );
97
103
 
98
104
  return (
@@ -164,10 +170,10 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
164
170
  <KContainer.VisibleView visible={Boolean(isPinned)}>
165
171
  <KContainer.View marginL="0.5rem">
166
172
  <KImage.VectorIcons
167
- name="pin-outline"
168
- provider="MaterialCommunityIcons"
169
- size={14}
170
- color={KColors.palette.gray.w400}
173
+ name={'thumbtack-b'}
174
+ provider="DroppiiNew"
175
+ size={16}
176
+ color={KColors.primary.normal}
171
177
  />
172
178
  </KContainer.View>
173
179
  </KContainer.VisibleView>
@@ -0,0 +1,26 @@
1
+ import { memo } from 'react';
2
+ import type { BaseLegendMessageProps } from '../types';
3
+ import { KContainer, KImage } from '@droppii/libs';
4
+ import { StyleSheet } from 'react-native';
5
+
6
+ const STICKER_SIZE = 120;
7
+ export const StickerMessage = memo(({ message }: BaseLegendMessageProps) => {
8
+ return (
9
+ <KContainer.View style={styles.message}>
10
+ <KImage.Base
11
+ uri={message?.stickerElem?.content || ''}
12
+ width={STICKER_SIZE}
13
+ height={STICKER_SIZE}
14
+ />
15
+ </KContainer.View>
16
+ );
17
+ });
18
+
19
+ const styles = StyleSheet.create({
20
+ message: {
21
+ width: STICKER_SIZE,
22
+ height: STICKER_SIZE,
23
+ justifyContent: 'center',
24
+ alignItems: 'center',
25
+ },
26
+ });
@@ -17,6 +17,9 @@ export interface ApiEndpoints {
17
17
  imageUpload: string;
18
18
  bot: string;
19
19
  uploadFcmToken: string;
20
+ stickerCategories: string;
21
+ stickersByCategory: string;
22
+ stickerHistory: string;
20
23
  }
21
24
 
22
25
  const DEFAULT_ENDPOINTS: ApiEndpoints = {
@@ -25,6 +28,9 @@ const DEFAULT_ENDPOINTS: ApiEndpoints = {
25
28
  imageUpload: '/uploader-service/v1/uploader/permanently',
26
29
  bot: '/chat-service/v1/app/bots/',
27
30
  uploadFcmToken: '/chat-service/third/fcm_update_token',
31
+ stickerCategories: '/chat-service/v1/app/sticker-categories',
32
+ stickersByCategory: '/chat-service/v1/app/sticker-categories',
33
+ stickerHistory: '/chat-service/v1/app/stickers/history',
28
34
  };
29
35
 
30
36
  let endpoints: ApiEndpoints = { ...DEFAULT_ENDPOINTS };
@@ -3,7 +3,7 @@ import OpenIMSDK, { SessionType } from '@droppii/openim-rn-client-sdk';
3
3
  import { useConversationStore } from '../../store/conversation';
4
4
  import { apiInstance } from '../../services/apis';
5
5
  import type { BaseResponse } from '../../types/auth';
6
- import type { DBotStatus } from '../../types/chat';
6
+ import type { DBotStatus, DConversationTarget } from '../../types/chat';
7
7
  import { conversationQueryKeys } from '../query-keys';
8
8
  import { ENDPOINTS } from '../../services/endpoints';
9
9
 
@@ -46,6 +46,25 @@ export const useGetStatusConversationBot = (botId?: string) => {
46
46
  refetchOnMount: 'always',
47
47
  });
48
48
  };
49
+ export const useGetConversationTarget = (conversationId?: string) =>
50
+ useQuery({
51
+ queryKey: conversationQueryKeys.conversationTarget(conversationId || ''),
52
+ queryFn: async () => {
53
+ try {
54
+ const res = await apiInstance?.get<BaseResponse<DConversationTarget>>(
55
+ ENDPOINTS.chatService.getConversationTarget(conversationId || '')
56
+ );
57
+ return res?.data?.data || undefined;
58
+ } catch {
59
+ return undefined; // fail-open: unresolved status => compose stays enabled
60
+ }
61
+ },
62
+ enabled: !!conversationId,
63
+ staleTime: 0,
64
+ gcTime: 0,
65
+ refetchOnMount: 'always',
66
+ });
67
+
49
68
  export const useGetOneConversation = ({
50
69
  targetUserId,
51
70
  targetGroupId,
@@ -12,7 +12,7 @@ export const useGetConversationFlowMainMenu = (conversationId?: string) => {
12
12
  const res = await apiInstance?.get<BaseResponse<InputButtonItem[]>>(
13
13
  ENDPOINTS.chatService.conversationFlowMainMenu(conversationId || '')
14
14
  );
15
- return res?.data?.data || undefined;
15
+ return res?.data?.data || ([] as InputButtonItem[]);
16
16
  },
17
17
  enabled: !!conversationId,
18
18
  });
@@ -16,10 +16,7 @@ import {
16
16
  type IMessageItemEx,
17
17
  } from '../../types/chat';
18
18
  import { useChatContext, useChatDetailContext } from '../../context';
19
- import {
20
- generateContentBasedOnMessageType,
21
- getBotId,
22
- } from '../../utils/conversation';
19
+ import { getBotId } from '../../utils/conversation';
23
20
  import { markMessageRevoked, useMessageStore } from '../../store/message';
24
21
  import uuid from 'react-native-uuid';
25
22
 
@@ -27,34 +24,36 @@ import type { IUrlMetadata } from '../../types/common';
27
24
  import type { IRunConversationFlowPayload } from '../../types/flows';
28
25
  import { DeviceEventEmitter } from 'react-native';
29
26
  import { ChatEvent } from '../../types/events';
27
+ import type { StickerItem } from '../../types/sticker';
28
+ import { useQueryClient } from '@tanstack/react-query';
29
+ import { stickerQueryKeys } from '../query-keys';
30
+ import { getMessagePreview } from '../../utils/messageUtils';
30
31
 
31
32
  const buildSendParams = (
32
33
  message: MessageItem,
33
34
  recvID: string,
34
35
  groupID: string,
35
- extendMessageInfo: IMessageItemEx,
36
- plainText: string
36
+ extendMessageInfo: IMessageItemEx
37
37
  ) => ({
38
38
  groupID,
39
39
  recvID,
40
40
  message: { ...message, ex: JSON.stringify(extendMessageInfo) || '{}' },
41
- offlinePushInfo: generateOfflinePushInfo(message.contentType, plainText),
41
+ offlinePushInfo: generateOfflinePushInfo(message),
42
42
  });
43
43
 
44
44
  export const useSendMessage = () => {
45
45
  const applicationType = useChatContext().applicationType;
46
46
  const pushNewMessage = useMessageStore((state) => state.pushNewMessage);
47
47
  const updateOneMessage = useMessageStore((state) => state.updateOneMessage);
48
- const deleteOneMessage = useMessageStore((state) => state.deleteOneMessage);
49
48
  const { quotedMessage, setQuotedMessage } = useChatDetailContext();
49
+ const queryClient = useQueryClient();
50
50
 
51
51
  const dispatchSend = useCallback(
52
52
  async (
53
53
  message: MessageItem,
54
54
  recvID: string,
55
55
  groupID: string,
56
- extendMessageInfo: IMessageItemEx,
57
- plainText: string
56
+ extendMessageInfo: IMessageItemEx
58
57
  ) => {
59
58
  pushNewMessage({ ...message, status: MessageStatus.Sending });
60
59
 
@@ -63,12 +62,10 @@ export const useSendMessage = () => {
63
62
  message,
64
63
  recvID,
65
64
  groupID,
66
- extendMessageInfo,
67
- plainText
65
+ extendMessageInfo
68
66
  );
69
67
  // console.log('[useSendMessage] dispatchSend params', sendParams);
70
68
  const sent = await OpenIMSDK.sendMessage(sendParams);
71
- deleteOneMessage(message.clientMsgID);
72
69
  pushNewMessage(sent);
73
70
  } catch {
74
71
  updateOneMessage({ ...message, status: MessageStatus.Failed });
@@ -76,7 +73,7 @@ export const useSendMessage = () => {
76
73
  await new Promise((resolve) => setTimeout(resolve, 300));
77
74
  DeviceEventEmitter.emit(ChatEvent.ScrollToBottom);
78
75
  },
79
- [pushNewMessage, updateOneMessage, deleteOneMessage]
76
+ [pushNewMessage, updateOneMessage]
80
77
  );
81
78
 
82
79
  const sendMessage = useCallback(
@@ -85,11 +82,13 @@ export const useSendMessage = () => {
85
82
  urlMetadata,
86
83
  flow,
87
84
  customMessage,
85
+ sticker,
88
86
  }: {
89
87
  plainText?: string;
90
88
  urlMetadata?: IUrlMetadata;
91
89
  flow?: IRunConversationFlowPayload;
92
90
  customMessage?: CustomMessagePayload;
91
+ sticker?: StickerItem;
93
92
  }) => {
94
93
  const currentConversation = useConversationStore
95
94
  .getState()
@@ -143,23 +142,39 @@ export const useSendMessage = () => {
143
142
  messageList.push(message);
144
143
  }
145
144
 
145
+ //sticker msg
146
+ if (!!sticker && !!sticker?.file?.fullUrl) {
147
+ let stickerMessage = await createStickerMessage(sticker?.file?.fullUrl);
148
+ if (!stickerMessage) return;
149
+ messageList.push(stickerMessage);
150
+ }
151
+
146
152
  for (const message of messageList) {
147
153
  const extendMessageInfo: IMessageItemEx = {
148
154
  applicationType,
149
155
  urlMetadata,
150
156
  flow,
157
+ stickerId: sticker?.id,
151
158
  };
152
159
 
153
160
  await dispatchSend(
154
161
  message,
155
162
  recvID || '',
156
163
  groupID || '',
157
- extendMessageInfo,
158
- plainText || ''
164
+ extendMessageInfo
159
165
  );
160
166
  }
167
+ if (!!sticker && !!sticker?.id) {
168
+ queryClient.invalidateQueries({ queryKey: stickerQueryKeys.recent() });
169
+ }
161
170
  },
162
- [applicationType, dispatchSend, quotedMessage, setQuotedMessage]
171
+ [
172
+ applicationType,
173
+ dispatchSend,
174
+ quotedMessage,
175
+ setQuotedMessage,
176
+ queryClient,
177
+ ]
163
178
  );
164
179
 
165
180
  const retryMessage = useCallback(
@@ -185,7 +200,7 @@ export const useSendMessage = () => {
185
200
  recvID: recvID || '',
186
201
  groupID: groupID || '',
187
202
  message,
188
- offlinePushInfo: generateOfflinePushInfo(message.contentType, ''),
203
+ offlinePushInfo: generateOfflinePushInfo(message),
189
204
  };
190
205
 
191
206
  try {
@@ -196,11 +211,9 @@ export const useSendMessage = () => {
196
211
  message,
197
212
  recvID || '',
198
213
  groupID || '',
199
- JSON.parse(message.ex || '{}') as IMessageItemEx,
200
- ''
214
+ JSON.parse(message.ex || '{}') as IMessageItemEx
201
215
  )
202
216
  );
203
- deleteOneMessage(message.clientMsgID);
204
217
  pushNewMessage(sent);
205
218
  } catch {
206
219
  updateOneMessage({ ...message, status: MessageStatus.Failed });
@@ -208,7 +221,7 @@ export const useSendMessage = () => {
208
221
  await new Promise((resolve) => setTimeout(resolve, 300));
209
222
  DeviceEventEmitter.emit(ChatEvent.ScrollToBottom);
210
223
  },
211
- [pushNewMessage, updateOneMessage, deleteOneMessage]
224
+ [pushNewMessage, updateOneMessage]
212
225
  );
213
226
 
214
227
  const revokeMessage = useCallback(async (message: MessageItem) => {
@@ -232,16 +245,13 @@ export const useSendMessage = () => {
232
245
  };
233
246
  };
234
247
 
235
- export const generateOfflinePushInfo = (
236
- contentType: MessageType,
237
- plainText: string
238
- ) => {
248
+ export const generateOfflinePushInfo = (message: MessageItem) => {
239
249
  const conversationData = useConversationStore
240
250
  ?.getState?.()
241
251
  ?.getCurrentConversation();
242
252
  const operatorUserID = useUserStore.getState().user?.userID || '';
243
253
  const title = conversationData?.showName || 'Droppii';
244
- const desc = generateContentBasedOnMessageType(contentType, plainText);
254
+ const desc = getMessagePreview(message);
245
255
  return {
246
256
  title,
247
257
  desc,
@@ -310,3 +320,15 @@ export const createQuotedMessage = async (params: QuoteMsgParams) => {
310
320
  });
311
321
  return quotedMessage;
312
322
  };
323
+
324
+ export const createStickerMessage = async (sticker: string) => {
325
+ let stickerMessage = await OpenIMSDK.createStickerMessage(sticker, uuid.v4())
326
+ .then((data) => {
327
+ return data;
328
+ })
329
+ .catch(({ errCode, errMsg }) => {
330
+ console.error('createStickerMessage', errCode, errMsg);
331
+ return null;
332
+ });
333
+ return stickerMessage;
334
+ };
@@ -12,6 +12,8 @@ export const conversationQueryKeys = {
12
12
  detail: (conversationId: string) =>
13
13
  [...conversationQueryKeys.details(), conversationId] as const,
14
14
  getStatusBot: (botId: string) => ['getStatusBot', botId] as const,
15
+ conversationTarget: (conversationId: string) =>
16
+ ['conversationTarget', conversationId] as const,
15
17
  flow: (conversationId: string) => ['conversationFlow', conversationId],
16
18
  flowMainMenu: (conversationId: string) => [
17
19
  'conversationFlowMainMenu',
@@ -34,3 +36,11 @@ export const usersQueryKeys = {
34
36
  };
35
37
 
36
38
  export type ConversationQueryKeys = typeof conversationQueryKeys;
39
+
40
+ export const stickerQueryKeys = {
41
+ all: ['stickers'] as const,
42
+ categories: () => [...stickerQueryKeys.all, 'categories'] as const,
43
+ byCategory: (categoryId: string) =>
44
+ [...stickerQueryKeys.all, 'category', categoryId] as const,
45
+ recent: () => [...stickerQueryKeys.all, 'recent'] as const,
46
+ };
@@ -0,0 +1,15 @@
1
+ import { useMutation } from '@tanstack/react-query';
2
+ import { apiInstance } from '../../services/apis';
3
+ import { ENDPOINTS } from '../../services/endpoints';
4
+ import type { BaseResponse } from '../../types/auth';
5
+ import type { StickerItem } from '../../types/sticker';
6
+
7
+ export const useFetchStickerByCategory = () =>
8
+ useMutation({
9
+ mutationFn: async (categoryId: string) => {
10
+ const res = await apiInstance?.get<BaseResponse<StickerItem[]>>(
11
+ ENDPOINTS.chatService.stickersByCategoryId(categoryId)
12
+ );
13
+ return res?.data?.data || [];
14
+ },
15
+ });
@@ -0,0 +1,17 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { apiInstance } from '../../services/apis';
3
+ import { stickerQueryKeys } from '../query-keys';
4
+ import { ENDPOINTS } from '../../services/endpoints';
5
+ import type { BaseResponse } from '../../types/auth';
6
+ import type { StickerCategory } from '../../types/sticker';
7
+
8
+ export const useStickerCategories = () =>
9
+ useQuery({
10
+ queryKey: stickerQueryKeys.categories(),
11
+ queryFn: async () => {
12
+ const res = await apiInstance?.get<BaseResponse<StickerCategory[]>>(
13
+ ENDPOINTS.chatService.stickerCategories()
14
+ );
15
+ return res?.data?.data || [];
16
+ },
17
+ });
@@ -0,0 +1,19 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { apiInstance } from '../../services/apis';
3
+ import { stickerQueryKeys } from '../query-keys';
4
+ import { ENDPOINTS } from '../../services/endpoints';
5
+ import type { BaseResponse } from '../../types/auth';
6
+ import type { StickerItem } from '../../types/sticker';
7
+
8
+ export const useStickerHistory = () =>
9
+ useQuery({
10
+ queryKey: stickerQueryKeys.recent(),
11
+ queryFn: async () => {
12
+ const res = await apiInstance?.get<BaseResponse<StickerItem[]>>(
13
+ ENDPOINTS.chatService.stickerHistory()
14
+ );
15
+ return res?.data?.data || [];
16
+ },
17
+ staleTime: 0,
18
+ gcTime: 0,
19
+ });