@droppii-org/chat-mobile 0.2.50 → 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 (137) 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/messages/stickerMessage/index.js +28 -0
  14. package/lib/module/components/messages/stickerMessage/index.js.map +1 -0
  15. package/lib/module/config/api-endpoints.js +4 -1
  16. package/lib/module/config/api-endpoints.js.map +1 -1
  17. package/lib/module/hooks/conversation/useGetConversationDetail.js +15 -0
  18. package/lib/module/hooks/conversation/useGetConversationDetail.js.map +1 -1
  19. package/lib/module/hooks/flows/useGetConversationFlowMainMenu.js +1 -1
  20. package/lib/module/hooks/flows/useGetConversationFlowMainMenu.js.map +1 -1
  21. package/lib/module/hooks/message/useSendMessage.js +45 -18
  22. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  23. package/lib/module/hooks/query-keys.js +7 -0
  24. package/lib/module/hooks/query-keys.js.map +1 -1
  25. package/lib/module/hooks/stickers/useFetchStickerByCategory.js +12 -0
  26. package/lib/module/hooks/stickers/useFetchStickerByCategory.js.map +1 -0
  27. package/lib/module/hooks/stickers/useStickerCategories.js +14 -0
  28. package/lib/module/hooks/stickers/useStickerCategories.js.map +1 -0
  29. package/lib/module/hooks/stickers/useStickerHistory.js +16 -0
  30. package/lib/module/hooks/stickers/useStickerHistory.js.map +1 -0
  31. package/lib/module/hooks/useChatCompose.js +1 -18
  32. package/lib/module/hooks/useChatCompose.js.map +1 -1
  33. package/lib/module/screens/chat-detail/ChatComposer.js +64 -16
  34. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  35. package/lib/module/screens/chat-detail/ChatDetail.js +6 -20
  36. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  37. package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js +15 -4
  38. package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js.map +1 -1
  39. package/lib/module/screens/chat-detail/hooks/useChatComposerState.js +3 -0
  40. package/lib/module/screens/chat-detail/hooks/useChatComposerState.js.map +1 -1
  41. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +5 -6
  42. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  43. package/lib/module/services/attachmentOrchestrator.js +1 -1
  44. package/lib/module/services/attachmentOrchestrator.js.map +1 -1
  45. package/lib/module/services/endpoints.js +4 -1
  46. package/lib/module/services/endpoints.js.map +1 -1
  47. package/lib/module/store/conversation.js +1 -20
  48. package/lib/module/store/conversation.js.map +1 -1
  49. package/lib/module/translation/resources/i18n.js +3 -1
  50. package/lib/module/translation/resources/i18n.js.map +1 -1
  51. package/lib/module/types/chat.js +6 -0
  52. package/lib/module/types/chat.js.map +1 -1
  53. package/lib/module/types/sticker.js +2 -0
  54. package/lib/module/types/sticker.js.map +1 -0
  55. package/lib/module/utils/conversation.js +1 -24
  56. package/lib/module/utils/conversation.js.map +1 -1
  57. package/lib/module/utils/messageUtils.js +2 -0
  58. package/lib/module/utils/messageUtils.js.map +1 -1
  59. package/lib/typescript/src/components/StickerPicker/StickerCategoryTabs.d.ts +9 -0
  60. package/lib/typescript/src/components/StickerPicker/StickerCategoryTabs.d.ts.map +1 -0
  61. package/lib/typescript/src/components/StickerPicker/StickerPickerPlaceholder.d.ts +2 -0
  62. package/lib/typescript/src/components/StickerPicker/StickerPickerPlaceholder.d.ts.map +1 -0
  63. package/lib/typescript/src/components/StickerPicker/StickerPickerRowItem.d.ts +8 -0
  64. package/lib/typescript/src/components/StickerPicker/StickerPickerRowItem.d.ts.map +1 -0
  65. package/lib/typescript/src/components/StickerPicker/StickerSectionList.d.ts +15 -0
  66. package/lib/typescript/src/components/StickerPicker/StickerSectionList.d.ts.map +1 -0
  67. package/lib/typescript/src/components/StickerPicker/constants.d.ts +11 -0
  68. package/lib/typescript/src/components/StickerPicker/constants.d.ts.map +1 -0
  69. package/lib/typescript/src/components/StickerPicker/index.d.ts +7 -0
  70. package/lib/typescript/src/components/StickerPicker/index.d.ts.map +1 -0
  71. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts +3 -0
  72. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts.map +1 -0
  73. package/lib/typescript/src/config/api-endpoints.d.ts +3 -0
  74. package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
  75. package/lib/typescript/src/hooks/conversation/useGetConversationDetail.d.ts +2 -1
  76. package/lib/typescript/src/hooks/conversation/useGetConversationDetail.d.ts.map +1 -1
  77. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +5 -3
  78. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  79. package/lib/typescript/src/hooks/query-keys.d.ts +7 -0
  80. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  81. package/lib/typescript/src/hooks/stickers/useFetchStickerByCategory.d.ts +3 -0
  82. package/lib/typescript/src/hooks/stickers/useFetchStickerByCategory.d.ts.map +1 -0
  83. package/lib/typescript/src/hooks/stickers/useStickerCategories.d.ts +3 -0
  84. package/lib/typescript/src/hooks/stickers/useStickerCategories.d.ts.map +1 -0
  85. package/lib/typescript/src/hooks/stickers/useStickerHistory.d.ts +3 -0
  86. package/lib/typescript/src/hooks/stickers/useStickerHistory.d.ts.map +1 -0
  87. package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -1
  88. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +2 -2
  89. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  90. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  91. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts +2 -1
  92. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts.map +1 -1
  93. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts +3 -0
  94. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts.map +1 -1
  95. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  96. package/lib/typescript/src/services/endpoints.d.ts +4 -1
  97. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  98. package/lib/typescript/src/store/conversation.d.ts +0 -3
  99. package/lib/typescript/src/store/conversation.d.ts.map +1 -1
  100. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  101. package/lib/typescript/src/types/chat.d.ts +12 -19
  102. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  103. package/lib/typescript/src/types/sticker.d.ts +21 -0
  104. package/lib/typescript/src/types/sticker.d.ts.map +1 -0
  105. package/lib/typescript/src/utils/conversation.d.ts +1 -2
  106. package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
  107. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  108. package/package.json +2 -2
  109. package/src/components/StickerPicker/StickerCategoryTabs.tsx +103 -0
  110. package/src/components/StickerPicker/StickerPickerPlaceholder.tsx +16 -0
  111. package/src/components/StickerPicker/StickerPickerRowItem.tsx +43 -0
  112. package/src/components/StickerPicker/StickerSectionList.tsx +141 -0
  113. package/src/components/StickerPicker/constants.ts +20 -0
  114. package/src/components/StickerPicker/index.tsx +166 -0
  115. package/src/components/messages/stickerMessage/index.tsx +26 -0
  116. package/src/config/api-endpoints.ts +6 -0
  117. package/src/hooks/conversation/useGetConversationDetail.ts +20 -1
  118. package/src/hooks/flows/useGetConversationFlowMainMenu.ts +1 -1
  119. package/src/hooks/message/useSendMessage.ts +49 -27
  120. package/src/hooks/query-keys.ts +10 -0
  121. package/src/hooks/stickers/useFetchStickerByCategory.ts +15 -0
  122. package/src/hooks/stickers/useStickerCategories.ts +17 -0
  123. package/src/hooks/stickers/useStickerHistory.ts +19 -0
  124. package/src/hooks/useChatCompose.ts +0 -19
  125. package/src/screens/chat-detail/ChatComposer.tsx +106 -19
  126. package/src/screens/chat-detail/ChatDetail.tsx +5 -19
  127. package/src/screens/chat-detail/hooks/useChatComposerAnimation.ts +21 -5
  128. package/src/screens/chat-detail/hooks/useChatComposerState.ts +7 -0
  129. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +4 -5
  130. package/src/services/attachmentOrchestrator.ts +1 -1
  131. package/src/services/endpoints.ts +6 -1
  132. package/src/store/conversation.ts +0 -24
  133. package/src/translation/resources/i18n.ts +3 -0
  134. package/src/types/chat.ts +13 -21
  135. package/src/types/sticker.ts +23 -0
  136. package/src/utils/conversation.ts +0 -28
  137. package/src/utils/messageUtils.ts +3 -0
@@ -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
+ });
@@ -13,9 +13,6 @@ import {
13
13
  generateAndUploadVideoThumbnails,
14
14
  type VideoWithUploadStatus,
15
15
  } from '../utils/videoThumbnailUpload';
16
- import { useConversationStore } from '../store';
17
- import { DChatCategory } from '../types/chat';
18
- import { useChatContext } from '../context';
19
16
 
20
17
  export function useChatCompose() {
21
18
  const [text, setText] = useState('');
@@ -25,7 +22,6 @@ export function useChatCompose() {
25
22
  const linkPreview = useLinkPreview(text);
26
23
  const { send: sendAttachments } = useSendAttachment();
27
24
  const { sendMessage } = useSendMessage();
28
- const { applicationType } = useChatContext();
29
25
 
30
26
  const isUploading = useMemo(
31
27
  () =>
@@ -53,20 +49,6 @@ export function useChatCompose() {
53
49
  const { selectedImages, clearAllImages } = imageAttachment;
54
50
  const { selectedFiles, clearAllFiles } = fileAttachment;
55
51
  const urlMeta = linkPreview.isVisible ? linkPreview.metadata : undefined;
56
- const currentConversationId =
57
- useConversationStore.getState().currentConversationId;
58
- const targetUserId = useConversationStore.getState().currentUserId;
59
- const isCreatingConversation =
60
- useConversationStore.getState().isCreatingConversation;
61
-
62
- if (targetUserId && !currentConversationId && !isCreatingConversation) {
63
- await useConversationStore.getState().createConversation({
64
- chatType: 'SINGLE',
65
- chatCategory: DChatCategory.BIZ_BIZ,
66
- applicationType,
67
- peerUserId: targetUserId,
68
- });
69
- }
70
52
 
71
53
  // --- Attachments ---
72
54
  if (selectedImages.length > 0 || selectedFiles.length > 0) {
@@ -179,7 +161,6 @@ export function useChatCompose() {
179
161
  sendAttachments,
180
162
  sendMessage,
181
163
  text,
182
- applicationType,
183
164
  ]);
184
165
 
185
166
  return {
@@ -8,6 +8,7 @@ import {
8
8
  type RefObject,
9
9
  } from 'react';
10
10
  import {
11
+ Keyboard,
11
12
  Platform,
12
13
  StyleSheet,
13
14
  type LayoutChangeEvent,
@@ -15,7 +16,10 @@ import {
15
16
  } from 'react-native';
16
17
  import { debounce } from 'lodash';
17
18
  import Reanimated from 'react-native-reanimated';
18
- import { KeyboardStickyView } from 'react-native-keyboard-controller';
19
+ import {
20
+ KeyboardStickyView,
21
+ useKeyboardState,
22
+ } from 'react-native-keyboard-controller';
19
23
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
20
24
  import { KContainer, KColors, KImage, KButton, KLabel } from '@droppii/libs';
21
25
 
@@ -30,12 +34,15 @@ import { ChatAttachmentPanel } from './ChatAttachmentPanel';
30
34
  import { ChatLinkPreview } from './ChatLinkPreview';
31
35
  import { ChatMessageInput } from './components/ChatMessageInput';
32
36
  import { AttachmentPreview } from '../../components/AttachmentPreview';
37
+ import { StickerPicker } from '../../components/StickerPicker';
38
+ import type { StickerItem } from '../../types/sticker';
33
39
 
34
40
  import {
35
41
  DEFAULT_QUICK_ACTIONS,
36
42
  getAttachmentPanelHeight,
37
43
  DEFAULT_PANNEL_ACTIONS,
38
44
  } from './constants';
45
+ import { STICKER_PICKER_HEIGHT } from '../../components/StickerPicker/constants';
39
46
  import { DChatPannelActionItemType, type DChatPannelAction } from './types';
40
47
  import { useChatComposerState } from './hooks/useChatComposerState';
41
48
  import { useChatComposerAnimation } from './hooks/useChatComposerAnimation';
@@ -47,11 +54,12 @@ import { useAttachmentMapping } from './hooks/useAttachmentMapping';
47
54
  import type { AttachmentType } from '../../types/attachment';
48
55
  import { useChatCompose } from '../../hooks/useChatCompose';
49
56
  import { ChatCurrentQuotedMessage } from './ChatCurrentQuotedMessage';
57
+ import { useSendMessage } from '../../hooks/message/useSendMessage';
50
58
 
51
59
  interface ChatComposerInternalProps {
52
60
  isEmptyMessage?: boolean;
53
61
  pannelActions?: DChatPannelAction[];
54
- isDisableCompose?: boolean;
62
+ isComposeDisabled?: boolean;
55
63
  composerRef?: RefObject<View | null>;
56
64
  onComposerLayout?: (event: LayoutChangeEvent) => void;
57
65
  }
@@ -60,18 +68,22 @@ export const ChatComposer = memo(
60
68
  ({
61
69
  isEmptyMessage = false,
62
70
  pannelActions,
63
- isDisableCompose = false,
71
+ isComposeDisabled = false,
64
72
  composerRef,
65
73
  onComposerLayout,
66
74
  }: ChatComposerInternalProps) => {
67
75
  const insets = useSafeAreaInsets();
76
+ const isDisableCompose = isComposeDisabled;
77
+ const isKeyboardVisible = useKeyboardState((state) => state.isVisible);
68
78
 
69
79
  const { isBotConversation, conversationFlow } = useChatDetailContext();
70
80
  const { mutate: runFlow, isPending } = useRunFlow();
71
-
81
+ const { sendMessage } = useSendMessage();
72
82
  const compose = useChatCompose();
73
83
 
74
84
  const {
85
+ activePanelType,
86
+ setActivePanelType,
75
87
  isAttachmentOpen,
76
88
  setIsAttachmentOpen,
77
89
  isInputFocused,
@@ -81,16 +93,25 @@ export const ChatComposer = memo(
81
93
  inputRef,
82
94
  } = useChatComposerState(compose.text);
83
95
 
84
- const panelHeight = useMemo(
96
+ const attachmentPanelHeight = useMemo(
85
97
  () => getAttachmentPanelHeight(DEFAULT_PANNEL_ACTIONS.length),
86
98
  []
87
99
  );
88
100
 
89
- const { accessorySlotStyle, toggleAttachmentVisibility, closeAttachment } =
90
- useChatComposerAnimation({
91
- panelHeight,
92
- onAttachmentOpenChange: setIsAttachmentOpen,
93
- });
101
+ const panelHeight =
102
+ activePanelType === 'sticker'
103
+ ? STICKER_PICKER_HEIGHT
104
+ : attachmentPanelHeight;
105
+
106
+ const {
107
+ accessorySlotStyle,
108
+ toggleAttachmentVisibility,
109
+ openWithHeight,
110
+ closeAttachment,
111
+ } = useChatComposerAnimation({
112
+ panelHeight,
113
+ onAttachmentOpenChange: setIsAttachmentOpen,
114
+ });
94
115
 
95
116
  const handleSend = useCallback(async () => {
96
117
  if (compose.isUploading || !compose.canSend) return;
@@ -103,8 +124,58 @@ export const ChatComposer = memo(
103
124
 
104
125
  const handleInputFocus = useCallback(() => {
105
126
  setIsInputFocused(true);
106
- if (isAttachmentOpen) closeAttachment(true);
107
- }, [closeAttachment, isAttachmentOpen, setIsInputFocused]);
127
+ if (isAttachmentOpen) {
128
+ // Use immediate close (no animation) when sticker panel is open to avoid
129
+ // layout conflict between the panel closing and keyboard appearing.
130
+ // Attachment panel is small enough that animation is fine (speedUp=true).
131
+ const immediate = activePanelType === 'sticker';
132
+ closeAttachment(!immediate, immediate);
133
+ setActivePanelType(null);
134
+ }
135
+ }, [
136
+ closeAttachment,
137
+ isAttachmentOpen,
138
+ activePanelType,
139
+ setIsInputFocused,
140
+ setActivePanelType,
141
+ ]);
142
+
143
+ const handleToggleAttachment = useCallback(() => {
144
+ if (activePanelType === 'sticker') {
145
+ // switch from sticker to attachment
146
+ setActivePanelType('attachment');
147
+ openWithHeight(attachmentPanelHeight);
148
+ return;
149
+ }
150
+ setActivePanelType(isAttachmentOpen ? null : 'attachment');
151
+ toggleAttachmentVisibility();
152
+ }, [
153
+ activePanelType,
154
+ isAttachmentOpen,
155
+ attachmentPanelHeight,
156
+ openWithHeight,
157
+ toggleAttachmentVisibility,
158
+ setActivePanelType,
159
+ ]);
160
+
161
+ const handleToggleSticker = useCallback(() => {
162
+ if (activePanelType === 'sticker') {
163
+ closeAttachment();
164
+ setActivePanelType(null);
165
+ return;
166
+ }
167
+ if (isKeyboardVisible) {
168
+ Keyboard.dismiss();
169
+ }
170
+ setActivePanelType('sticker');
171
+ openWithHeight(STICKER_PICKER_HEIGHT);
172
+ }, [
173
+ activePanelType,
174
+ isKeyboardVisible,
175
+ closeAttachment,
176
+ openWithHeight,
177
+ setActivePanelType,
178
+ ]);
108
179
 
109
180
  const handleInputBlur = useCallback(() => {
110
181
  setIsInputFocused(false);
@@ -181,6 +252,13 @@ export const ChatComposer = memo(
181
252
  [insets.bottom, isAttachmentOpen]
182
253
  );
183
254
 
255
+ const handleStickerSelect = useCallback(
256
+ (sticker: StickerItem) => {
257
+ sendMessage({ sticker });
258
+ },
259
+ [sendMessage]
260
+ );
261
+
184
262
  const attachments = useAttachmentMapping({
185
263
  selectedImages: compose.selectedImages,
186
264
  selectedFiles: compose.selectedFiles,
@@ -211,7 +289,7 @@ export const ChatComposer = memo(
211
289
  />
212
290
  </KContainer.View>
213
291
  <KLabel.Text typo="TextXsNormal">
214
- Người dùng này không còn khả dụng
292
+ {trans('chat:compose_disabled_seller_inactive')}
215
293
  </KLabel.Text>
216
294
  </KContainer.View>
217
295
  );
@@ -265,15 +343,19 @@ export const ChatComposer = memo(
265
343
  style={inputRowStyle}
266
344
  >
267
345
  <KContainer.Touchable
268
- onPress={toggleAttachmentVisibility}
346
+ onPress={handleToggleAttachment}
269
347
  center
270
348
  br="round"
271
349
  >
272
350
  <KImage.VectorIcons
273
- name={isAttachmentOpen ? 'close-circle-o' : 'plus-circle-o'}
351
+ name={
352
+ activePanelType === 'attachment'
353
+ ? 'close-circle-o'
354
+ : 'plus-circle-o'
355
+ }
274
356
  size={24}
275
357
  color={
276
- isAttachmentOpen
358
+ activePanelType === 'attachment'
277
359
  ? KColors.palette.primary.w400
278
360
  : KColors.palette.gray.w600
279
361
  }
@@ -290,6 +372,7 @@ export const ChatComposer = memo(
290
372
  isInputFocused={isInputFocused}
291
373
  isMultilineInput={isMultilineInput}
292
374
  inputRef={inputRef}
375
+ onEmojiPress={handleToggleSticker}
293
376
  />
294
377
 
295
378
  <KContainer.Touchable
@@ -308,8 +391,7 @@ export const ChatComposer = memo(
308
391
  opacity={compose.isUploading ? 0.6 : 1}
309
392
  >
310
393
  <KImage.VectorIcons
311
- name="send"
312
- provider="MaterialCommunityIcons"
394
+ name="send-b"
313
395
  size={20}
314
396
  color={KColors.white}
315
397
  />
@@ -335,7 +417,12 @@ export const ChatComposer = memo(
335
417
  style={[styles.accessorySlot, accessorySlotStyle]}
336
418
  pointerEvents={isAttachmentOpen ? 'auto' : 'none'}
337
419
  >
338
- <ChatAttachmentPanel actions={resolvedPannelActons} />
420
+ {activePanelType === 'attachment' && (
421
+ <ChatAttachmentPanel actions={resolvedPannelActons} />
422
+ )}
423
+ {activePanelType === 'sticker' && (
424
+ <StickerPicker onSelectSticker={handleStickerSelect} />
425
+ )}
339
426
  </Reanimated.View>
340
427
  </KContainer.VisibleView>
341
428
  </>
@@ -1,4 +1,4 @@
1
- import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
1
+ import { memo, useCallback, useEffect, useRef } from 'react';
2
2
  import { DeviceEventEmitter } from 'react-native';
3
3
 
4
4
  import { useKeyboardScrollToEnd } from '@legendapp/list/keyboard';
@@ -26,15 +26,12 @@ import type { ChatDetailProps } from './types';
26
26
  import { normalizeUrl } from '../../utils/url';
27
27
  import { MessageToolboxOverlay } from './messageToolbox/MessageToolboxOverlay';
28
28
  import type { MeasuredRect, MessageToolboxRef } from './messageToolbox/types';
29
- import { DBotStatusValue } from '../../types/chat';
30
29
  import {
31
30
  useGetConversationDetail,
32
- useGetStatusConversationBot,
31
+ useGetConversationTarget,
33
32
  } from '../../hooks/conversation/useGetConversationDetail';
34
- import { PeerType } from '@droppii/openim-rn-client-sdk';
35
33
  import type { MessageItem } from '@droppii/openim-rn-client-sdk';
36
34
  import { useMessages } from '../../hooks/message/useMessages';
37
- import { getBotId } from '../../utils/conversation';
38
35
  import { ChatEvent } from '../../types/events';
39
36
  import { useSetIsShowFLashMessage } from '../../context/global';
40
37
 
@@ -69,19 +66,8 @@ const ChatDetail = memo(
69
66
 
70
67
  const conversation = useConversation(conversationId);
71
68
 
72
- const botId = useMemo(() => {
73
- if (conversation?.peerType === PeerType.Bot) {
74
- if (targetBotId) {
75
- return targetBotId;
76
- }
77
- return getBotId(conversation);
78
- }
79
- return undefined;
80
- }, [conversation, targetBotId]);
81
-
82
- const { data: botInfo } = useGetStatusConversationBot(botId);
83
-
84
- const { status: botStatus } = botInfo || {};
69
+ const { data: target } = useGetConversationTarget(conversationId);
70
+ const isComposeDisabled = !!target && !target.isActive;
85
71
 
86
72
  const handleBackDefault = useCallback(() => {
87
73
  navigation.goBack();
@@ -205,7 +191,7 @@ const ChatDetail = memo(
205
191
  <ChatComposer
206
192
  isEmptyMessage={messages?.length === 0}
207
193
  pannelActions={pannelActions}
208
- isDisableCompose={botStatus === DBotStatusValue.INACTIVE}
194
+ isComposeDisabled={isComposeDisabled}
209
195
  />
210
196
 
211
197
  <MediaViewerModal ref={mediaViewerRef} />
@@ -19,7 +19,8 @@ interface UseChatComposerAnimationReturn {
19
19
  accessorySlotStyle: any;
20
20
  toggleAttachmentVisibility: () => void;
21
21
  openAttachment: () => void;
22
- closeAttachment: (speedUp?: boolean) => void;
22
+ openWithHeight: (height: number) => void;
23
+ closeAttachment: (speedUp?: boolean, immediate?: boolean) => void;
23
24
  }
24
25
 
25
26
  export const useChatComposerAnimation = ({
@@ -43,10 +44,14 @@ export const useChatComposerAnimation = ({
43
44
  }, [attachmentHeightSV, panelHeight, onAttachmentOpenChange]);
44
45
 
45
46
  const closeAttachment = useCallback(
46
- (speedUp = false) => {
47
- attachmentHeightSV.value = withTiming(0, {
48
- duration: ACCESSORY_ANIMATION_MS / (speedUp ? 2 : 1),
49
- });
47
+ (speedUp = false, immediate = false) => {
48
+ if (immediate) {
49
+ attachmentHeightSV.value = 0;
50
+ } else {
51
+ attachmentHeightSV.value = withTiming(0, {
52
+ duration: ACCESSORY_ANIMATION_MS / (speedUp ? 2 : 1),
53
+ });
54
+ }
50
55
  onAttachmentOpenChange(false);
51
56
  },
52
57
  [attachmentHeightSV, onAttachmentOpenChange]
@@ -56,6 +61,16 @@ export const useChatComposerAnimation = ({
56
61
  return { height: attachmentHeightSV.value };
57
62
  });
58
63
 
64
+ const openWithHeight = useCallback(
65
+ (height: number) => {
66
+ attachmentHeightSV.value = withTiming(height, {
67
+ duration: ACCESSORY_ANIMATION_MS,
68
+ });
69
+ onAttachmentOpenChange(true);
70
+ },
71
+ [attachmentHeightSV, onAttachmentOpenChange]
72
+ );
73
+
59
74
  const toggleAttachmentVisibility = useCallback(() => {
60
75
  // Use animated value as source of truth, not stale state
61
76
  const isOpen = attachmentHeightSV.value > 0;
@@ -84,6 +99,7 @@ export const useChatComposerAnimation = ({
84
99
  accessorySlotStyle,
85
100
  toggleAttachmentVisibility,
86
101
  openAttachment,
102
+ openWithHeight,
87
103
  closeAttachment,
88
104
  };
89
105
  };