@droppii-org/chat-mobile 0.2.50 → 0.2.52

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 (248) 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/resource-center-empty-box.png +0 -0
  4. package/lib/module/components/MediaViewerModal.js +2 -1
  5. package/lib/module/components/MediaViewerModal.js.map +1 -1
  6. package/lib/module/components/MergedImageGrid.js +1 -0
  7. package/lib/module/components/MergedImageGrid.js.map +1 -1
  8. package/lib/module/components/StickerPicker/StickerCategoryTabs.js +86 -0
  9. package/lib/module/components/StickerPicker/StickerCategoryTabs.js.map +1 -0
  10. package/lib/module/components/StickerPicker/StickerPickerPlaceholder.js +18 -0
  11. package/lib/module/components/StickerPicker/StickerPickerPlaceholder.js.map +1 -0
  12. package/lib/module/components/StickerPicker/StickerPickerRowItem.js +35 -0
  13. package/lib/module/components/StickerPicker/StickerPickerRowItem.js.map +1 -0
  14. package/lib/module/components/StickerPicker/StickerSectionList.js +110 -0
  15. package/lib/module/components/StickerPicker/StickerSectionList.js.map +1 -0
  16. package/lib/module/components/StickerPicker/constants.js +14 -0
  17. package/lib/module/components/StickerPicker/constants.js.map +1 -0
  18. package/lib/module/components/StickerPicker/index.js +134 -0
  19. package/lib/module/components/StickerPicker/index.js.map +1 -0
  20. package/lib/module/components/messages/stickerMessage/index.js +28 -0
  21. package/lib/module/components/messages/stickerMessage/index.js.map +1 -0
  22. package/lib/module/config/api-endpoints.js +5 -1
  23. package/lib/module/config/api-endpoints.js.map +1 -1
  24. package/lib/module/core/notification/pushToken.js +7 -7
  25. package/lib/module/core/notification/pushToken.js.map +1 -1
  26. package/lib/module/hooks/conversation/useGetConversationDetail.js +15 -0
  27. package/lib/module/hooks/conversation/useGetConversationDetail.js.map +1 -1
  28. package/lib/module/hooks/flows/useGetConversationFlowMainMenu.js +1 -1
  29. package/lib/module/hooks/flows/useGetConversationFlowMainMenu.js.map +1 -1
  30. package/lib/module/hooks/message/useSendMessage.js +45 -18
  31. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  32. package/lib/module/hooks/query-keys.js +11 -0
  33. package/lib/module/hooks/query-keys.js.map +1 -1
  34. package/lib/module/hooks/resourceCenter/useThreadResources.js +116 -0
  35. package/lib/module/hooks/resourceCenter/useThreadResources.js.map +1 -0
  36. package/lib/module/hooks/stickers/useFetchStickerByCategory.js +12 -0
  37. package/lib/module/hooks/stickers/useFetchStickerByCategory.js.map +1 -0
  38. package/lib/module/hooks/stickers/useStickerCategories.js +14 -0
  39. package/lib/module/hooks/stickers/useStickerCategories.js.map +1 -0
  40. package/lib/module/hooks/stickers/useStickerHistory.js +16 -0
  41. package/lib/module/hooks/stickers/useStickerHistory.js.map +1 -0
  42. package/lib/module/hooks/useChatCompose.js +1 -18
  43. package/lib/module/hooks/useChatCompose.js.map +1 -1
  44. package/lib/module/hooks/useImageAttachment.js +0 -2
  45. package/lib/module/hooks/useImageAttachment.js.map +1 -1
  46. package/lib/module/hooks/useMediaViewer.js +4 -1
  47. package/lib/module/hooks/useMediaViewer.js.map +1 -1
  48. package/lib/module/hooks/useSendAttachment.js +4 -1
  49. package/lib/module/hooks/useSendAttachment.js.map +1 -1
  50. package/lib/module/index.js +1 -0
  51. package/lib/module/index.js.map +1 -1
  52. package/lib/module/screens/MediaView/index.js +104 -42
  53. package/lib/module/screens/MediaView/index.js.map +1 -1
  54. package/lib/module/screens/ResourceCenter/FileResourceRow.js +80 -0
  55. package/lib/module/screens/ResourceCenter/FileResourceRow.js.map +1 -0
  56. package/lib/module/screens/ResourceCenter/GridRenderer.js +83 -0
  57. package/lib/module/screens/ResourceCenter/GridRenderer.js.map +1 -0
  58. package/lib/module/screens/ResourceCenter/GridThumbnail.js +97 -0
  59. package/lib/module/screens/ResourceCenter/GridThumbnail.js.map +1 -0
  60. package/lib/module/screens/ResourceCenter/LinkResourceRow.js +81 -0
  61. package/lib/module/screens/ResourceCenter/LinkResourceRow.js.map +1 -0
  62. package/lib/module/screens/ResourceCenter/ListRenderer.js +60 -0
  63. package/lib/module/screens/ResourceCenter/ListRenderer.js.map +1 -0
  64. package/lib/module/screens/ResourceCenter/ResourceCenterTabBar.js +75 -0
  65. package/lib/module/screens/ResourceCenter/ResourceCenterTabBar.js.map +1 -0
  66. package/lib/module/screens/ResourceCenter/ResourceCenterTabScene.js +122 -0
  67. package/lib/module/screens/ResourceCenter/ResourceCenterTabScene.js.map +1 -0
  68. package/lib/module/screens/ResourceCenter/ResourceEmptyState.js +36 -0
  69. package/lib/module/screens/ResourceCenter/ResourceEmptyState.js.map +1 -0
  70. package/lib/module/screens/ResourceCenter/index.js +104 -0
  71. package/lib/module/screens/ResourceCenter/index.js.map +1 -0
  72. package/lib/module/screens/ResourceCenter/types.js +25 -0
  73. package/lib/module/screens/ResourceCenter/types.js.map +1 -0
  74. package/lib/module/screens/chat-detail/ChatComposer.js +64 -16
  75. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  76. package/lib/module/screens/chat-detail/ChatDetail.js +6 -20
  77. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  78. package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js +15 -4
  79. package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js.map +1 -1
  80. package/lib/module/screens/chat-detail/hooks/useChatComposerState.js +3 -0
  81. package/lib/module/screens/chat-detail/hooks/useChatComposerState.js.map +1 -1
  82. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +5 -6
  83. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  84. package/lib/module/services/attachmentOrchestrator.js +20 -5
  85. package/lib/module/services/attachmentOrchestrator.js.map +1 -1
  86. package/lib/module/services/endpoints.js +4 -1
  87. package/lib/module/services/endpoints.js.map +1 -1
  88. package/lib/module/services/resourceCenter.js +102 -0
  89. package/lib/module/services/resourceCenter.js.map +1 -0
  90. package/lib/module/store/conversation.js +1 -20
  91. package/lib/module/store/conversation.js.map +1 -1
  92. package/lib/module/translation/resources/i18n.js +10 -1
  93. package/lib/module/translation/resources/i18n.js.map +1 -1
  94. package/lib/module/types/attachment.js +2 -0
  95. package/lib/module/types/chat.js +26 -0
  96. package/lib/module/types/chat.js.map +1 -1
  97. package/lib/module/types/sticker.js +2 -0
  98. package/lib/module/types/sticker.js.map +1 -0
  99. package/lib/module/utils/conversation.js +1 -24
  100. package/lib/module/utils/conversation.js.map +1 -1
  101. package/lib/module/utils/messageUtils.js +2 -0
  102. package/lib/module/utils/messageUtils.js.map +1 -1
  103. package/lib/module/utils/url.js +11 -0
  104. package/lib/module/utils/url.js.map +1 -1
  105. package/lib/typescript/src/assets/images/index.d.ts +1 -0
  106. package/lib/typescript/src/assets/images/index.d.ts.map +1 -1
  107. package/lib/typescript/src/components/MediaViewerModal.d.ts +3 -1
  108. package/lib/typescript/src/components/MediaViewerModal.d.ts.map +1 -1
  109. package/lib/typescript/src/components/MergedImageGrid.d.ts.map +1 -1
  110. package/lib/typescript/src/components/StickerPicker/StickerCategoryTabs.d.ts +9 -0
  111. package/lib/typescript/src/components/StickerPicker/StickerCategoryTabs.d.ts.map +1 -0
  112. package/lib/typescript/src/components/StickerPicker/StickerPickerPlaceholder.d.ts +2 -0
  113. package/lib/typescript/src/components/StickerPicker/StickerPickerPlaceholder.d.ts.map +1 -0
  114. package/lib/typescript/src/components/StickerPicker/StickerPickerRowItem.d.ts +8 -0
  115. package/lib/typescript/src/components/StickerPicker/StickerPickerRowItem.d.ts.map +1 -0
  116. package/lib/typescript/src/components/StickerPicker/StickerSectionList.d.ts +15 -0
  117. package/lib/typescript/src/components/StickerPicker/StickerSectionList.d.ts.map +1 -0
  118. package/lib/typescript/src/components/StickerPicker/constants.d.ts +11 -0
  119. package/lib/typescript/src/components/StickerPicker/constants.d.ts.map +1 -0
  120. package/lib/typescript/src/components/StickerPicker/index.d.ts +7 -0
  121. package/lib/typescript/src/components/StickerPicker/index.d.ts.map +1 -0
  122. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts +3 -0
  123. package/lib/typescript/src/components/messages/stickerMessage/index.d.ts.map +1 -0
  124. package/lib/typescript/src/config/api-endpoints.d.ts +4 -0
  125. package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
  126. package/lib/typescript/src/hooks/conversation/useGetConversationDetail.d.ts +2 -1
  127. package/lib/typescript/src/hooks/conversation/useGetConversationDetail.d.ts.map +1 -1
  128. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +5 -3
  129. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  130. package/lib/typescript/src/hooks/query-keys.d.ts +11 -0
  131. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  132. package/lib/typescript/src/hooks/resourceCenter/useThreadResources.d.ts +21 -0
  133. package/lib/typescript/src/hooks/resourceCenter/useThreadResources.d.ts.map +1 -0
  134. package/lib/typescript/src/hooks/stickers/useFetchStickerByCategory.d.ts +3 -0
  135. package/lib/typescript/src/hooks/stickers/useFetchStickerByCategory.d.ts.map +1 -0
  136. package/lib/typescript/src/hooks/stickers/useStickerCategories.d.ts +3 -0
  137. package/lib/typescript/src/hooks/stickers/useStickerCategories.d.ts.map +1 -0
  138. package/lib/typescript/src/hooks/stickers/useStickerHistory.d.ts +3 -0
  139. package/lib/typescript/src/hooks/stickers/useStickerHistory.d.ts.map +1 -0
  140. package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -1
  141. package/lib/typescript/src/hooks/useImageAttachment.d.ts.map +1 -1
  142. package/lib/typescript/src/hooks/useMediaViewer.d.ts +5 -1
  143. package/lib/typescript/src/hooks/useMediaViewer.d.ts.map +1 -1
  144. package/lib/typescript/src/hooks/useSendAttachment.d.ts.map +1 -1
  145. package/lib/typescript/src/index.d.ts +2 -0
  146. package/lib/typescript/src/index.d.ts.map +1 -1
  147. package/lib/typescript/src/screens/MediaView/index.d.ts.map +1 -1
  148. package/lib/typescript/src/screens/ResourceCenter/FileResourceRow.d.ts +7 -0
  149. package/lib/typescript/src/screens/ResourceCenter/FileResourceRow.d.ts.map +1 -0
  150. package/lib/typescript/src/screens/ResourceCenter/GridRenderer.d.ts +14 -0
  151. package/lib/typescript/src/screens/ResourceCenter/GridRenderer.d.ts.map +1 -0
  152. package/lib/typescript/src/screens/ResourceCenter/GridThumbnail.d.ts +10 -0
  153. package/lib/typescript/src/screens/ResourceCenter/GridThumbnail.d.ts.map +1 -0
  154. package/lib/typescript/src/screens/ResourceCenter/LinkResourceRow.d.ts +8 -0
  155. package/lib/typescript/src/screens/ResourceCenter/LinkResourceRow.d.ts.map +1 -0
  156. package/lib/typescript/src/screens/ResourceCenter/ListRenderer.d.ts +16 -0
  157. package/lib/typescript/src/screens/ResourceCenter/ListRenderer.d.ts.map +1 -0
  158. package/lib/typescript/src/screens/ResourceCenter/ResourceCenterTabBar.d.ts +3 -0
  159. package/lib/typescript/src/screens/ResourceCenter/ResourceCenterTabBar.d.ts.map +1 -0
  160. package/lib/typescript/src/screens/ResourceCenter/ResourceCenterTabScene.d.ts +16 -0
  161. package/lib/typescript/src/screens/ResourceCenter/ResourceCenterTabScene.d.ts.map +1 -0
  162. package/lib/typescript/src/screens/ResourceCenter/ResourceEmptyState.d.ts +6 -0
  163. package/lib/typescript/src/screens/ResourceCenter/ResourceEmptyState.d.ts.map +1 -0
  164. package/lib/typescript/src/screens/ResourceCenter/index.d.ts +4 -0
  165. package/lib/typescript/src/screens/ResourceCenter/index.d.ts.map +1 -0
  166. package/lib/typescript/src/screens/ResourceCenter/types.d.ts +28 -0
  167. package/lib/typescript/src/screens/ResourceCenter/types.d.ts.map +1 -0
  168. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +2 -2
  169. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  170. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  171. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts +2 -1
  172. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts.map +1 -1
  173. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts +3 -0
  174. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts.map +1 -1
  175. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  176. package/lib/typescript/src/services/attachmentOrchestrator.d.ts.map +1 -1
  177. package/lib/typescript/src/services/endpoints.d.ts +4 -1
  178. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  179. package/lib/typescript/src/services/resourceCenter.d.ts +9 -0
  180. package/lib/typescript/src/services/resourceCenter.d.ts.map +1 -0
  181. package/lib/typescript/src/store/conversation.d.ts +0 -3
  182. package/lib/typescript/src/store/conversation.d.ts.map +1 -1
  183. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  184. package/lib/typescript/src/types/attachment.d.ts +2 -0
  185. package/lib/typescript/src/types/attachment.d.ts.map +1 -1
  186. package/lib/typescript/src/types/chat.d.ts +66 -15
  187. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  188. package/lib/typescript/src/types/sticker.d.ts +21 -0
  189. package/lib/typescript/src/types/sticker.d.ts.map +1 -0
  190. package/lib/typescript/src/utils/conversation.d.ts +1 -2
  191. package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
  192. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  193. package/lib/typescript/src/utils/url.d.ts +7 -0
  194. package/lib/typescript/src/utils/url.d.ts.map +1 -1
  195. package/package.json +2 -2
  196. package/src/assets/images/index.ts +1 -0
  197. package/src/assets/images/resource-center-empty-box.png +0 -0
  198. package/src/components/MediaViewerModal.tsx +8 -3
  199. package/src/components/MergedImageGrid.tsx +1 -0
  200. package/src/components/StickerPicker/StickerCategoryTabs.tsx +103 -0
  201. package/src/components/StickerPicker/StickerPickerPlaceholder.tsx +16 -0
  202. package/src/components/StickerPicker/StickerPickerRowItem.tsx +43 -0
  203. package/src/components/StickerPicker/StickerSectionList.tsx +141 -0
  204. package/src/components/StickerPicker/constants.ts +20 -0
  205. package/src/components/StickerPicker/index.tsx +166 -0
  206. package/src/components/messages/stickerMessage/index.tsx +26 -0
  207. package/src/config/api-endpoints.ts +8 -0
  208. package/src/core/notification/pushToken.ts +7 -7
  209. package/src/hooks/conversation/useGetConversationDetail.ts +20 -1
  210. package/src/hooks/flows/useGetConversationFlowMainMenu.ts +1 -1
  211. package/src/hooks/message/useSendMessage.ts +49 -27
  212. package/src/hooks/query-keys.ts +16 -0
  213. package/src/hooks/resourceCenter/useThreadResources.ts +124 -0
  214. package/src/hooks/stickers/useFetchStickerByCategory.ts +15 -0
  215. package/src/hooks/stickers/useStickerCategories.ts +17 -0
  216. package/src/hooks/stickers/useStickerHistory.ts +19 -0
  217. package/src/hooks/useChatCompose.ts +0 -19
  218. package/src/hooks/useImageAttachment.ts +1 -2
  219. package/src/hooks/useMediaViewer.ts +21 -7
  220. package/src/hooks/useSendAttachment.ts +4 -1
  221. package/src/index.tsx +2 -0
  222. package/src/screens/MediaView/index.tsx +117 -54
  223. package/src/screens/ResourceCenter/FileResourceRow.tsx +99 -0
  224. package/src/screens/ResourceCenter/GridRenderer.tsx +104 -0
  225. package/src/screens/ResourceCenter/GridThumbnail.tsx +98 -0
  226. package/src/screens/ResourceCenter/LinkResourceRow.tsx +96 -0
  227. package/src/screens/ResourceCenter/ListRenderer.tsx +81 -0
  228. package/src/screens/ResourceCenter/ResourceCenterTabBar.tsx +89 -0
  229. package/src/screens/ResourceCenter/ResourceCenterTabScene.tsx +142 -0
  230. package/src/screens/ResourceCenter/ResourceEmptyState.tsx +39 -0
  231. package/src/screens/ResourceCenter/index.tsx +129 -0
  232. package/src/screens/ResourceCenter/types.ts +56 -0
  233. package/src/screens/chat-detail/ChatComposer.tsx +106 -19
  234. package/src/screens/chat-detail/ChatDetail.tsx +5 -19
  235. package/src/screens/chat-detail/hooks/useChatComposerAnimation.ts +21 -5
  236. package/src/screens/chat-detail/hooks/useChatComposerState.ts +7 -0
  237. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +4 -5
  238. package/src/services/attachmentOrchestrator.ts +16 -3
  239. package/src/services/endpoints.ts +6 -1
  240. package/src/services/resourceCenter.ts +137 -0
  241. package/src/store/conversation.ts +0 -24
  242. package/src/translation/resources/i18n.ts +10 -0
  243. package/src/types/attachment.ts +3 -0
  244. package/src/types/chat.ts +80 -15
  245. package/src/types/sticker.ts +23 -0
  246. package/src/utils/conversation.ts +0 -28
  247. package/src/utils/messageUtils.ts +3 -0
  248. package/src/utils/url.ts +11 -0
@@ -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 {
@@ -171,8 +171,7 @@ export const useImageAttachment = (): UseImageAttachmentReturn => {
171
171
  const maxSize = isVideoFromPicker(item)
172
172
  ? UPLOAD_LIMITS.video
173
173
  : UPLOAD_LIMITS.image;
174
- console.log('maxSize', maxSize);
175
- console.log('item.size', item.size);
174
+
176
175
  return (item.size || 0) <= maxSize;
177
176
  };
178
177
 
@@ -5,11 +5,20 @@ export interface MediaItem {
5
5
  isVideo?: boolean;
6
6
  }
7
7
 
8
+ export interface ShowMediaViewerOptions {
9
+ pagination?: boolean;
10
+ }
11
+
8
12
  export interface UseMediaViewerReturn {
9
13
  visible: boolean;
10
14
  mediaList: MediaItem[];
11
15
  initIndex: number;
12
- show: (items: MediaItem[], index?: number) => void;
16
+ pagination: boolean;
17
+ show: (
18
+ items: MediaItem[],
19
+ index?: number,
20
+ options?: ShowMediaViewerOptions
21
+ ) => void;
13
22
  hide: () => void;
14
23
  }
15
24
 
@@ -17,16 +26,21 @@ export function useMediaViewer(): UseMediaViewerReturn {
17
26
  const [visible, setVisible] = useState(false);
18
27
  const [mediaList, setMediaList] = useState<MediaItem[]>([]);
19
28
  const [initIndex, setInitIndex] = useState(0);
29
+ const [pagination, setPagination] = useState(true);
20
30
 
21
- const show = useCallback((items: MediaItem[], index = 0) => {
22
- setMediaList(items);
23
- setInitIndex(index);
24
- setVisible(true);
25
- }, []);
31
+ const show = useCallback(
32
+ (items: MediaItem[], index = 0, options?: ShowMediaViewerOptions) => {
33
+ setMediaList(items);
34
+ setInitIndex(index);
35
+ setPagination(options?.pagination ?? true);
36
+ setVisible(true);
37
+ },
38
+ []
39
+ );
26
40
 
27
41
  const hide = useCallback(() => {
28
42
  setVisible(false);
29
43
  }, []);
30
44
 
31
- return { visible, mediaList, initIndex, show, hide };
45
+ return { visible, mediaList, initIndex, pagination, show, hide };
32
46
  }
@@ -2,6 +2,7 @@ import { useCallback, useState } from 'react';
2
2
  import { AttachmentOrchestratorService } from '../services/attachmentOrchestrator';
3
3
  import type { Attachment, AttachmentSendOptions } from '../types/attachment';
4
4
  import { getMimeType } from '../utils/imageUtils';
5
+ import { useChatContext } from '../context';
5
6
 
6
7
  export interface ImageItem {
7
8
  type: 'image';
@@ -70,6 +71,7 @@ export interface UseSendAttachmentReturn {
70
71
  export function useSendAttachment(): UseSendAttachmentReturn {
71
72
  const [isLoading, setIsLoading] = useState(false);
72
73
  const [error, setError] = useState<string | null>(null);
74
+ const applicationType = useChatContext().applicationType;
73
75
 
74
76
  const send = useCallback(
75
77
  async (
@@ -242,6 +244,7 @@ export function useSendAttachment(): UseSendAttachmentReturn {
242
244
  uploadedUrls,
243
245
  {
244
246
  strategy: 'individual',
247
+ applicationType,
245
248
  ...options,
246
249
  }
247
250
  );
@@ -278,7 +281,7 @@ export function useSendAttachment(): UseSendAttachmentReturn {
278
281
  console.log('[useSendAttachment] Finished');
279
282
  }
280
283
  },
281
- []
284
+ [applicationType]
282
285
  );
283
286
 
284
287
  const clearError = useCallback(() => setError(null), []);
package/src/index.tsx CHANGED
@@ -28,6 +28,8 @@ export { AttachmentPreview } from './components/AttachmentPreview';
28
28
  export { MediaViewerModal } from './components/MediaViewerModal';
29
29
  export { Inbox } from './screens/inbox';
30
30
  export { default as MediaViewerScreen } from './screens/MediaView';
31
+ export { default as ResourceCenterScreen } from './screens/ResourceCenter';
32
+ export type { ResourceCenterScreenProps } from './screens/ResourceCenter/types';
31
33
  export * from './screens/chat-detail';
32
34
 
33
35
  export type {
@@ -1,14 +1,18 @@
1
1
  import { useCallback, useEffect, useRef, useState, useMemo } from 'react';
2
2
  import type { ComponentType } from 'react';
3
3
  import {
4
+ ActivityIndicator,
4
5
  BackHandler,
5
6
  Platform,
6
7
  StyleSheet,
7
- FlatList,
8
- type ListRenderItemInfo,
9
8
  type ViewToken,
10
9
  StatusBar,
11
10
  } from 'react-native';
11
+ import {
12
+ LegendList,
13
+ type LegendListRef,
14
+ type LegendListRenderItemProps,
15
+ } from '@legendapp/list/react-native';
12
16
 
13
17
  import LinearGradient from 'react-native-linear-gradient';
14
18
  import Animated, {
@@ -19,6 +23,9 @@ import Animated, {
19
23
  } from 'react-native-reanimated';
20
24
  import { Pagination } from 'react-native-snap-carousel';
21
25
  import PhotoView from 'react-native-photo-view';
26
+ import RNFS from 'react-native-fs';
27
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
28
+ import { saveDocuments } from '@react-native-documents/picker';
22
29
  import {
23
30
  KColors,
24
31
  KContainer,
@@ -29,6 +36,9 @@ import {
29
36
  KSpacingValue,
30
37
  } from '@droppii/libs';
31
38
  import VideoPlayer from './VideoPlayer';
39
+ import { UIUtils } from '../../utils/UIUtils';
40
+ import { trans } from '../../translation';
41
+ import { getMimeType } from '../../utils/imageUtils';
32
42
 
33
43
  interface MediaViewerParams {
34
44
  images: any[];
@@ -49,15 +59,62 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
49
59
  onDismiss,
50
60
  } = props;
51
61
 
52
- const listRef = useRef<FlatList>(null);
62
+ const insets = useSafeAreaInsets();
63
+ const listRef = useRef<LegendListRef>(null);
53
64
  const [index, setIndex] = useState(initIndex);
54
65
  const [isScrolling, setIsScrolling] = useState(false);
66
+ const [isDownloading, setIsDownloading] = useState(false);
55
67
 
56
68
  const dismiss = useCallback(() => {
57
69
  onDismiss?.();
58
70
  }, [onDismiss]);
59
71
 
60
- const onDownload = useCallback(() => {}, []);
72
+ // Downloads the currently-viewed image/video, mirroring `useFileDownload`'s
73
+ // RNFS-download + Save-As-document pattern (no gallery/CameraRoll dependency
74
+ // exists in this repo — Save As is the only working "download" mechanism here).
75
+ const onDownload = useCallback(async () => {
76
+ const item = images[index];
77
+ if (!item?.url || isDownloading) return;
78
+
79
+ setIsDownloading(true);
80
+ try {
81
+ const urlPath = item.url.split('?')[0];
82
+ const fallbackExt = item.isVideo ? 'mp4' : 'jpg';
83
+ const fileName =
84
+ urlPath.split('/').pop() ||
85
+ `${item.isVideo ? 'video' : 'image'}-${index}.${fallbackExt}`;
86
+ const destPath = `${RNFS.CachesDirectoryPath}/${fileName}`;
87
+
88
+ const exists = await RNFS.exists(destPath);
89
+ if (!exists) {
90
+ const result = await RNFS.downloadFile({
91
+ fromUrl: item.url,
92
+ toFile: destPath,
93
+ }).promise;
94
+ if (result.statusCode < 200 || result.statusCode >= 300) {
95
+ throw new Error(`HTTP ${result.statusCode}`);
96
+ }
97
+ }
98
+
99
+ await saveDocuments({
100
+ sourceUris: [`file://${destPath}`],
101
+ mimeType: getMimeType(fileName),
102
+ fileName,
103
+ copy: true,
104
+ });
105
+ UIUtils.toast.open({
106
+ title: trans('chat:file_downloaded_success', { fileName }),
107
+ theme: 'info',
108
+ });
109
+ } catch {
110
+ UIUtils.toast.open({
111
+ title: trans('chat:file_download_failed'),
112
+ theme: 'warning',
113
+ });
114
+ } finally {
115
+ setIsDownloading(false);
116
+ }
117
+ }, [images, index, isDownloading]);
61
118
 
62
119
  useEffect(() => {
63
120
  StatusBar.setHidden(true);
@@ -79,22 +136,6 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
79
136
  const onMomentumScrollBegin = useCallback(() => setIsScrolling(true), []);
80
137
  const onMomentumScrollEnd = useCallback(() => setIsScrolling(false), []);
81
138
 
82
- const onScrollToIndexFailed = useCallback(
83
- (info: any) => {
84
- const len = images.length;
85
- const wait = new Promise((resolve) => setTimeout(() => resolve(true), 0));
86
- wait.then(() => {
87
- if (info.index > -1 && info.index < len) {
88
- listRef.current?.scrollToIndex({
89
- index: info.index,
90
- animated: false,
91
- });
92
- }
93
- });
94
- },
95
- [images]
96
- );
97
-
98
139
  const onViewableItemsChanged = useRef(
99
140
  ({ viewableItems }: { viewableItems: ViewToken[] }) => {
100
141
  const i = viewableItems[0]?.index;
@@ -105,7 +146,7 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
105
146
  const keyEx = useCallback((item: any, i: number) => `${item.url}-${i}`, []);
106
147
 
107
148
  const renderItem = useCallback(
108
- ({ item, index: itemIndex }: ListRenderItemInfo<any>) => (
149
+ ({ item, index: itemIndex }: LegendListRenderItemProps<any>) => (
109
150
  <KContainer.Touchable center activeOpacity={1}>
110
151
  <KContainer.VisibleView visible={!!item.isVideo}>
111
152
  <VideoPlayer
@@ -130,9 +171,21 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
130
171
  );
131
172
 
132
173
  const renderFooter = useMemo(() => {
133
- if (pagination === false) return null;
174
+ // `pagination` only toggles the dot indicator (noisy on large sets like
175
+ // Resource Center grids) — the current-index counter should always show.
176
+ if (images.length <= 1) return null;
134
177
  return (
135
- <KContainer.View style={styles.footer}>
178
+ <KContainer.View
179
+ style={[
180
+ styles.footer,
181
+ {
182
+ bottom:
183
+ insets.bottom > 0
184
+ ? insets.bottom + KSpacingValue['0.5rem']
185
+ : KSpacingValue['2rem'],
186
+ },
187
+ ]}
188
+ >
136
189
  <KLabel.Text
137
190
  typo="TextNmNormal"
138
191
  color={KColors.white}
@@ -140,18 +193,20 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
140
193
  >
141
194
  {index + 1}/{images.length}
142
195
  </KLabel.Text>
143
- <Pagination
144
- dotContainerStyle={styles.dotContainerStyle}
145
- dotStyle={styles.dot}
146
- inactiveDotStyle={styles.inactiveDot}
147
- inactiveDotScale={0.6}
148
- dotsLength={images.length > 1 ? images.length : 3}
149
- activeDotIndex={images.length > 1 ? index : 1}
150
- inactiveDotOpacity={images.length > 1 ? 0.5 : 0}
151
- />
196
+ <KContainer.VisibleView visible={pagination !== false}>
197
+ <Pagination
198
+ dotContainerStyle={styles.dotContainerStyle}
199
+ dotStyle={styles.dot}
200
+ inactiveDotStyle={styles.inactiveDot}
201
+ inactiveDotScale={0.6}
202
+ dotsLength={images.length}
203
+ activeDotIndex={index}
204
+ inactiveDotOpacity={0.5}
205
+ />
206
+ </KContainer.VisibleView>
152
207
  </KContainer.View>
153
208
  );
154
- }, [pagination, images.length, index]);
209
+ }, [pagination, images.length, index, insets.bottom]);
155
210
  return (
156
211
  <Animated.View
157
212
  style={styles.wrapper}
@@ -166,7 +221,7 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
166
221
  {/* Top gradient overlay */}
167
222
  <LinearGradient
168
223
  colors={['rgba(0,0,0,0.9)', 'rgba(0,0,0,0)']}
169
- style={[styles.topOverlay]}
224
+ style={[styles.topOverlay, { paddingTop: insets.top }]}
170
225
  pointerEvents="box-none"
171
226
  >
172
227
  {/* Header first row — matches Figma: [close] [flex-1 spacer] [action-group] */}
@@ -184,22 +239,33 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
184
239
  <KContainer.View flex />
185
240
 
186
241
  {/* Download button */}
187
- {download && (
242
+ <KContainer.VisibleView visible={!!download}>
188
243
  <KContainer.Touchable
189
244
  style={styles.headerBtn}
190
- onPress={onDownload}
245
+ onPress={isDownloading ? undefined : onDownload}
246
+ disabled={isDownloading}
191
247
  >
192
- <KImage.VectorIcons
193
- name="download-o"
194
- size={24}
195
- color={KColors.white}
196
- />
248
+ <KContainer.VisibleView visible={isDownloading}>
249
+ <ActivityIndicator size="small" color={KColors.white} />
250
+ </KContainer.VisibleView>
251
+ <KContainer.VisibleView visible={!isDownloading}>
252
+ <KImage.VectorIcons
253
+ name="download-o"
254
+ size={24}
255
+ color={KColors.white}
256
+ />
257
+ </KContainer.VisibleView>
197
258
  </KContainer.Touchable>
198
- )}
259
+ </KContainer.VisibleView>
199
260
  </KContainer.View>
200
261
  </LinearGradient>
201
- {/* Media content */}
202
- <FlatList
262
+ {/* Media content. `recycleItems` intentionally NOT set — `VideoPlayer`
263
+ wraps a native player instance keyed off mount, not just the `url`
264
+ prop, so reusing a recycled cell across a video/image swap risks
265
+ stale playback state. Dataset here is always small (one message's
266
+ attachments or a Resource Center snapshot), so the perf win isn't
267
+ worth that risk. */}
268
+ <LegendList
203
269
  ref={listRef}
204
270
  initialScrollIndex={
205
271
  initIndex >= images?.length ? images?.length - 1 : initIndex
@@ -207,17 +273,14 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
207
273
  data={images}
208
274
  renderItem={renderItem}
209
275
  keyExtractor={keyEx}
276
+ estimatedItemSize={KDims.width}
210
277
  pagingEnabled
211
278
  horizontal
212
279
  showsHorizontalScrollIndicator={false}
213
280
  onViewableItemsChanged={onViewableItemsChanged}
214
281
  viewabilityConfig={{ itemVisiblePercentThreshold: 50 }}
215
- onScrollToIndexFailed={onScrollToIndexFailed}
216
282
  onMomentumScrollBegin={onMomentumScrollBegin}
217
283
  onMomentumScrollEnd={onMomentumScrollEnd}
218
- removeClippedSubviews
219
- maxToRenderPerBatch={2}
220
- windowSize={3}
221
284
  style={styles.flex}
222
285
  />
223
286
 
@@ -244,13 +307,14 @@ const styles = StyleSheet.create({
244
307
  height: KDims.height,
245
308
  },
246
309
  topOverlay: {
247
- // position: 'absolute',
310
+ // `paddingTop` is overridden inline with `useSafeAreaInsets().top` (real
311
+ // notch/status-bar inset, both platforms).
312
+ position: 'absolute',
248
313
  top: 0,
249
314
  left: 0,
250
315
  right: 0,
251
316
  zIndex: 100,
252
317
  minHeight: 100,
253
- paddingTop: 30,
254
318
  },
255
319
  // Figma: Header-First-Row — flex row, padding 8, alignItems center
256
320
  headerRow: {
@@ -303,11 +367,10 @@ const styles = StyleSheet.create({
303
367
  letterSpacing: 0.12,
304
368
  },
305
369
  footer: {
370
+ // `bottom` is overridden inline with `useSafeAreaInsets()` (real device
371
+ // inset, both platforms) — `KDims.bottomSafeHeight` only covers iPhone
372
+ // notch/Dynamic Island and misses Android gesture-nav insets.
306
373
  position: 'absolute',
307
- bottom:
308
- KDims.bottomSafeHeight > 0
309
- ? KDims.bottomSafeHeight + KSpacingValue['0.5rem']
310
- : KSpacingValue['2rem'],
311
374
  alignSelf: 'center',
312
375
  alignItems: 'center',
313
376
  zIndex: 10,
@@ -0,0 +1,99 @@
1
+ import { memo, useMemo } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import {
4
+ KContainer,
5
+ KImage,
6
+ KLabel,
7
+ KColors,
8
+ KSpacingValue,
9
+ } from '@droppii/libs';
10
+ import { useFileDownload } from '../../hooks/useFileDownload';
11
+ import {
12
+ getFileIcon,
13
+ getMimeType,
14
+ formatFileSize,
15
+ } from '../../utils/imageUtils';
16
+ import { trans } from '../../translation';
17
+ import type { DThreadResource } from '../../types/chat';
18
+
19
+ interface Props {
20
+ item: DThreadResource;
21
+ }
22
+
23
+ export const FileResourceRow = memo(({ item }: Props) => {
24
+ const fileName = item.fileName || '[File]';
25
+ const url = item.sourceUrl || '';
26
+ const { state, handleView } = useFileDownload(fileName, url);
27
+
28
+ const mimeType = useMemo(
29
+ () => item.mimeType || getMimeType(fileName),
30
+ [item.mimeType, fileName]
31
+ );
32
+ const iconSource = useMemo(() => getFileIcon(mimeType), [mimeType]);
33
+
34
+ const subtitle = useMemo(() => {
35
+ if (state.status === 'downloading') {
36
+ return trans('chat:file_downloading', {
37
+ received: state.receivedMB ?? 0,
38
+ total: state.totalMB ?? 0,
39
+ });
40
+ }
41
+ if (state.status === 'error') {
42
+ return state.error ?? trans('chat:file_download_failed');
43
+ }
44
+ return formatFileSize(item.fileSize ?? 0);
45
+ }, [state, item.fileSize]);
46
+
47
+ const subtitleColor =
48
+ state.status === 'error'
49
+ ? (KColors.palette.danger?.w400 ?? '#FF3B30')
50
+ : KColors.palette.gray.w400;
51
+
52
+ // Figma's File-Item component carries a trailing "Close-Button" (X in a dark
53
+ // circle) layer, but it's `visible: false` in every real row — the rendered
54
+ // design has NO trailing icon at all. Confirmed by product decision: no
55
+ // separate download button — the whole row is tappable (`handleView`, same
56
+ // cache-then-open behavior the in-chat `FileMessage` bubble's eye icon uses).
57
+ return (
58
+ <KContainer.Touchable
59
+ style={styles.row}
60
+ br="3x"
61
+ background={KColors.opacity.grayLighter[20]}
62
+ onPress={handleView}
63
+ >
64
+ <KImage.Base uri={iconSource} style={styles.icon} resizeMode="contain" />
65
+ <KContainer.View flexS={1}>
66
+ <KLabel.Text
67
+ typo="TextMdMedium"
68
+ color={KColors.palette.gray.w900}
69
+ numberOfLines={1}
70
+ ellipsizeMode="middle"
71
+ >
72
+ {fileName}
73
+ </KLabel.Text>
74
+ <KLabel.Text
75
+ typo="TextSmNormal"
76
+ color={subtitleColor}
77
+ numberOfLines={1}
78
+ >
79
+ {subtitle}
80
+ </KLabel.Text>
81
+ </KContainer.View>
82
+ </KContainer.Touchable>
83
+ );
84
+ });
85
+
86
+ const styles = StyleSheet.create({
87
+ // Figma File-Item (`14208:88679`): 12px padding all sides, 8px gap, 40x40 icon.
88
+ row: {
89
+ flexDirection: 'row',
90
+ alignItems: 'center',
91
+ gap: KSpacingValue['0.5rem'],
92
+ padding: KSpacingValue['0.75rem'],
93
+ marginBottom: KSpacingValue['0.5rem'],
94
+ },
95
+ icon: {
96
+ width: 40,
97
+ height: 40,
98
+ },
99
+ });
@@ -0,0 +1,104 @@
1
+ import { memo, useCallback, useMemo } from 'react';
2
+ import type { ReactElement } from 'react';
3
+ import {
4
+ ActivityIndicator,
5
+ StyleSheet,
6
+ useWindowDimensions,
7
+ } from 'react-native';
8
+ import { LegendList } from '@legendapp/list/react-native';
9
+ import { KContainer, KSpacingValue, KColors } from '@droppii/libs';
10
+ import { GridThumbnail } from './GridThumbnail';
11
+ import type { DThreadResource } from '../../types/chat';
12
+
13
+ const COLUMNS = 3;
14
+ const GRID_GAP = KSpacingValue['0.5rem']; // 8px
15
+ const CONTAINER_PADDING = KSpacingValue['0.75rem']; // 12px
16
+
17
+ interface Props {
18
+ items: DThreadResource[];
19
+ onEndReached: () => void;
20
+ isFetchingNextPage: boolean;
21
+ onPressItem: (index: number) => void;
22
+ onRefresh: () => void;
23
+ isRefetching: boolean;
24
+ emptyComponent: ReactElement;
25
+ }
26
+
27
+ export const GridRenderer = memo(
28
+ ({
29
+ items,
30
+ onEndReached,
31
+ isFetchingNextPage,
32
+ onPressItem,
33
+ onRefresh,
34
+ isRefetching,
35
+ emptyComponent,
36
+ }: Props) => {
37
+ // Reactive — do NOT use KDims.width (static snapshot, stale on rotation/resize).
38
+ const { width } = useWindowDimensions();
39
+ const cellSize = useMemo(() => {
40
+ const raw =
41
+ (width - CONTAINER_PADDING * 2 - GRID_GAP * (COLUMNS - 1)) / COLUMNS;
42
+ // Floor against a degenerate width (extremely narrow split-screen, or a
43
+ // not-yet-measured `0` on first paint) — a <= 0 `estimatedItemSize` would
44
+ // feed LegendList's internal layout math a divide-by-zero/negative size.
45
+ return Number.isFinite(raw) && raw > 0 ? raw : 1;
46
+ }, [width]);
47
+
48
+ const renderItem = useCallback(
49
+ ({ item, index }: { item: DThreadResource; index: number }) => (
50
+ <GridThumbnail
51
+ item={item}
52
+ index={index}
53
+ size={cellSize}
54
+ onPress={onPressItem}
55
+ />
56
+ ),
57
+ [cellSize, onPressItem]
58
+ );
59
+
60
+ const keyExtractor = useCallback((item: DThreadResource) => item.id, []);
61
+ return (
62
+ <LegendList
63
+ data={items}
64
+ numColumns={COLUMNS}
65
+ estimatedItemSize={cellSize}
66
+ renderItem={renderItem}
67
+ keyExtractor={keyExtractor}
68
+ // `GridThumbnail` is stateless/presentational (derives everything from
69
+ // props, no internal `useState`) — safe to recycle cell instances
70
+ // instead of remounting on scroll. Without this, LegendList defaults to
71
+ // `false` and logs its own dev warning recommending exactly this.
72
+ recycleItems
73
+ contentContainerStyle={styles.content}
74
+ columnWrapperStyle={styles.row}
75
+ onEndReached={onEndReached}
76
+ onEndReachedThreshold={0.5}
77
+ onRefresh={onRefresh}
78
+ refreshing={isRefetching}
79
+ ListEmptyComponent={emptyComponent}
80
+ ListFooterComponent={
81
+ <KContainer.VisibleView visible={isFetchingNextPage}>
82
+ <ActivityIndicator
83
+ style={styles.footer}
84
+ color={KColors.palette.primary.w400}
85
+ />
86
+ </KContainer.VisibleView>
87
+ }
88
+ />
89
+ );
90
+ }
91
+ );
92
+
93
+ const styles = StyleSheet.create({
94
+ content: {
95
+ padding: CONTAINER_PADDING,
96
+ gap: GRID_GAP,
97
+ },
98
+ row: {
99
+ gap: GRID_GAP,
100
+ },
101
+ footer: {
102
+ paddingVertical: KSpacingValue['1rem'],
103
+ },
104
+ });