@amityco/react-native-social-uikit 4.1.1-fe2678f6.0 → 4.2.0

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 (205) hide show
  1. package/README.md +7 -0
  2. package/lib/commonjs/core/assets/icons/post.js +1 -1
  3. package/lib/commonjs/core/assets/icons/post.js.map +1 -1
  4. package/lib/commonjs/core/assets/icons/story.js +1 -1
  5. package/lib/commonjs/core/assets/icons/story.js.map +1 -1
  6. package/lib/commonjs/core/assets/icons/xml.js +16 -2
  7. package/lib/commonjs/core/assets/icons/xml.js.map +1 -1
  8. package/lib/commonjs/core/legacy/file.js +145 -62
  9. package/lib/commonjs/core/legacy/file.js.map +1 -1
  10. package/lib/commonjs/core/stores/slices/feedSlice.js +3 -0
  11. package/lib/commonjs/core/stores/slices/feedSlice.js.map +1 -1
  12. package/lib/commonjs/core/stores/slices/globalfeedSlice.js +5 -0
  13. package/lib/commonjs/core/stores/slices/globalfeedSlice.js.map +1 -1
  14. package/lib/commonjs/core/stores/slices/localVideoThumbnailSlice.js +46 -0
  15. package/lib/commonjs/core/stores/slices/localVideoThumbnailSlice.js.map +1 -0
  16. package/lib/commonjs/core/stores/slices/uiSlice.js +5 -1
  17. package/lib/commonjs/core/stores/slices/uiSlice.js.map +1 -1
  18. package/lib/commonjs/core/stores/store/index.js +3 -1
  19. package/lib/commonjs/core/stores/store/index.js.map +1 -1
  20. package/lib/commonjs/social/components/LoadingImage/index.js +73 -12
  21. package/lib/commonjs/social/components/LoadingImage/index.js.map +1 -1
  22. package/lib/commonjs/social/components/LoadingImage/styles.js +8 -3
  23. package/lib/commonjs/social/components/LoadingImage/styles.js.map +1 -1
  24. package/lib/commonjs/social/components/LoadingVideo/index.js +90 -16
  25. package/lib/commonjs/social/components/LoadingVideo/index.js.map +1 -1
  26. package/lib/commonjs/social/components/LoadingVideo/styles.js +14 -5
  27. package/lib/commonjs/social/components/LoadingVideo/styles.js.map +1 -1
  28. package/lib/commonjs/social/components/MediaViewer/MediaViewer.js +13 -13
  29. package/lib/commonjs/social/components/MediaViewer/MediaViewer.js.map +1 -1
  30. package/lib/commonjs/social/components/MediaViewer/styles.js +11 -1
  31. package/lib/commonjs/social/components/MediaViewer/styles.js.map +1 -1
  32. package/lib/commonjs/social/components/PostContent/index.js +8 -29
  33. package/lib/commonjs/social/components/PostContent/index.js.map +1 -1
  34. package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js +109 -17
  35. package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -1
  36. package/lib/commonjs/social/components/legacy/LoadingVideo/index.js +17 -6
  37. package/lib/commonjs/social/components/legacy/LoadingVideo/index.js.map +1 -1
  38. package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js +121 -23
  39. package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js.map +1 -1
  40. package/lib/commonjs/social/elements/PostMediaElement/styles.js +7 -0
  41. package/lib/commonjs/social/elements/PostMediaElement/styles.js.map +1 -1
  42. package/lib/commonjs/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js +11 -5
  43. package/lib/commonjs/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js.map +1 -1
  44. package/lib/commonjs/social/features/feed/components/NewsFeed/NewsFeed.js +14 -0
  45. package/lib/commonjs/social/features/feed/components/NewsFeed/NewsFeed.js.map +1 -1
  46. package/lib/commonjs/social/features/post/Composer/PostComposer.js +234 -86
  47. package/lib/commonjs/social/features/post/Composer/PostComposer.js.map +1 -1
  48. package/lib/commonjs/social/hooks/index.js +11 -0
  49. package/lib/commonjs/social/hooks/index.js.map +1 -1
  50. package/lib/commonjs/social/hooks/useCommunities.js +5 -3
  51. package/lib/commonjs/social/hooks/useCommunities.js.map +1 -1
  52. package/lib/commonjs/social/hooks/useCommunityPermission.js +43 -0
  53. package/lib/commonjs/social/hooks/useCommunityPermission.js.map +1 -0
  54. package/lib/commonjs/social/hooks/useCustomRankingGlobalFeed.js +10 -1
  55. package/lib/commonjs/social/hooks/useCustomRankingGlobalFeed.js.map +1 -1
  56. package/lib/commonjs/social/hooks/useFile.js +11 -0
  57. package/lib/commonjs/social/hooks/useFile.js.map +1 -1
  58. package/lib/commonjs/social/screens/CommunityProfile/components/Header/Header.js +18 -7
  59. package/lib/commonjs/social/screens/CommunityProfile/components/Header/Header.js.map +1 -1
  60. package/lib/commonjs/social/utils/mediaAttachments.js +150 -0
  61. package/lib/commonjs/social/utils/mediaAttachments.js.map +1 -0
  62. package/lib/commonjs/social/utils/postChildren.js +72 -0
  63. package/lib/commonjs/social/utils/postChildren.js.map +1 -0
  64. package/lib/module/core/assets/icons/post.js +1 -1
  65. package/lib/module/core/assets/icons/post.js.map +1 -1
  66. package/lib/module/core/assets/icons/story.js +1 -1
  67. package/lib/module/core/assets/icons/story.js.map +1 -1
  68. package/lib/module/core/assets/icons/xml.js +13 -0
  69. package/lib/module/core/assets/icons/xml.js.map +1 -1
  70. package/lib/module/core/legacy/file.js +145 -62
  71. package/lib/module/core/legacy/file.js.map +1 -1
  72. package/lib/module/core/stores/slices/feedSlice.js +3 -0
  73. package/lib/module/core/stores/slices/feedSlice.js.map +1 -1
  74. package/lib/module/core/stores/slices/globalfeedSlice.js +5 -0
  75. package/lib/module/core/stores/slices/globalfeedSlice.js.map +1 -1
  76. package/lib/module/core/stores/slices/localVideoThumbnailSlice.js +41 -0
  77. package/lib/module/core/stores/slices/localVideoThumbnailSlice.js.map +1 -0
  78. package/lib/module/core/stores/slices/uiSlice.js +5 -1
  79. package/lib/module/core/stores/slices/uiSlice.js.map +1 -1
  80. package/lib/module/core/stores/store/index.js +3 -1
  81. package/lib/module/core/stores/store/index.js.map +1 -1
  82. package/lib/module/social/components/LoadingImage/index.js +74 -14
  83. package/lib/module/social/components/LoadingImage/index.js.map +1 -1
  84. package/lib/module/social/components/LoadingImage/styles.js +8 -3
  85. package/lib/module/social/components/LoadingImage/styles.js.map +1 -1
  86. package/lib/module/social/components/LoadingVideo/index.js +91 -17
  87. package/lib/module/social/components/LoadingVideo/index.js.map +1 -1
  88. package/lib/module/social/components/LoadingVideo/styles.js +14 -5
  89. package/lib/module/social/components/LoadingVideo/styles.js.map +1 -1
  90. package/lib/module/social/components/MediaViewer/MediaViewer.js +13 -13
  91. package/lib/module/social/components/MediaViewer/MediaViewer.js.map +1 -1
  92. package/lib/module/social/components/MediaViewer/styles.js +11 -1
  93. package/lib/module/social/components/MediaViewer/styles.js.map +1 -1
  94. package/lib/module/social/components/PostContent/index.js +8 -29
  95. package/lib/module/social/components/PostContent/index.js.map +1 -1
  96. package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js +110 -18
  97. package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -1
  98. package/lib/module/social/components/legacy/LoadingVideo/index.js +17 -6
  99. package/lib/module/social/components/legacy/LoadingVideo/index.js.map +1 -1
  100. package/lib/module/social/elements/PostMediaElement/PostMediaElement.js +123 -25
  101. package/lib/module/social/elements/PostMediaElement/PostMediaElement.js.map +1 -1
  102. package/lib/module/social/elements/PostMediaElement/styles.js +7 -0
  103. package/lib/module/social/elements/PostMediaElement/styles.js.map +1 -1
  104. package/lib/module/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js +12 -6
  105. package/lib/module/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js.map +1 -1
  106. package/lib/module/social/features/feed/components/NewsFeed/NewsFeed.js +15 -1
  107. package/lib/module/social/features/feed/components/NewsFeed/NewsFeed.js.map +1 -1
  108. package/lib/module/social/features/post/Composer/PostComposer.js +236 -89
  109. package/lib/module/social/features/post/Composer/PostComposer.js.map +1 -1
  110. package/lib/module/social/hooks/index.js +1 -0
  111. package/lib/module/social/hooks/index.js.map +1 -1
  112. package/lib/module/social/hooks/useCommunities.js +5 -3
  113. package/lib/module/social/hooks/useCommunities.js.map +1 -1
  114. package/lib/module/social/hooks/useCommunityPermission.js +37 -0
  115. package/lib/module/social/hooks/useCommunityPermission.js.map +1 -0
  116. package/lib/module/social/hooks/useCustomRankingGlobalFeed.js +10 -1
  117. package/lib/module/social/hooks/useCustomRankingGlobalFeed.js.map +1 -1
  118. package/lib/module/social/hooks/useFile.js +11 -0
  119. package/lib/module/social/hooks/useFile.js.map +1 -1
  120. package/lib/module/social/screens/CommunityProfile/components/Header/Header.js +19 -8
  121. package/lib/module/social/screens/CommunityProfile/components/Header/Header.js.map +1 -1
  122. package/lib/module/social/utils/mediaAttachments.js +139 -0
  123. package/lib/module/social/utils/mediaAttachments.js.map +1 -0
  124. package/lib/module/social/utils/postChildren.js +66 -0
  125. package/lib/module/social/utils/postChildren.js.map +1 -0
  126. package/lib/typescript/core/assets/icons/xml.d.ts +1 -0
  127. package/lib/typescript/core/assets/icons/xml.d.ts.map +1 -1
  128. package/lib/typescript/core/legacy/file.d.ts +32 -0
  129. package/lib/typescript/core/legacy/file.d.ts.map +1 -1
  130. package/lib/typescript/core/stores/slices/feedSlice.d.ts.map +1 -1
  131. package/lib/typescript/core/stores/slices/globalfeedSlice.d.ts.map +1 -1
  132. package/lib/typescript/core/stores/slices/localVideoThumbnailSlice.d.ts +35 -0
  133. package/lib/typescript/core/stores/slices/localVideoThumbnailSlice.d.ts.map +1 -0
  134. package/lib/typescript/core/stores/slices/uiSlice.d.ts +5 -0
  135. package/lib/typescript/core/stores/slices/uiSlice.d.ts.map +1 -1
  136. package/lib/typescript/core/stores/store/index.d.ts.map +1 -1
  137. package/lib/typescript/core/types/mention.d.ts +34 -0
  138. package/lib/typescript/core/types/mention.d.ts.map +1 -1
  139. package/lib/typescript/social/components/LoadingImage/index.d.ts +8 -2
  140. package/lib/typescript/social/components/LoadingImage/index.d.ts.map +1 -1
  141. package/lib/typescript/social/components/LoadingImage/styles.d.ts.map +1 -1
  142. package/lib/typescript/social/components/LoadingVideo/index.d.ts +9 -2
  143. package/lib/typescript/social/components/LoadingVideo/index.d.ts.map +1 -1
  144. package/lib/typescript/social/components/LoadingVideo/styles.d.ts +1 -0
  145. package/lib/typescript/social/components/LoadingVideo/styles.d.ts.map +1 -1
  146. package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts.map +1 -1
  147. package/lib/typescript/social/components/MediaViewer/styles.d.ts +1 -0
  148. package/lib/typescript/social/components/MediaViewer/styles.d.ts.map +1 -1
  149. package/lib/typescript/social/components/PostContent/index.d.ts.map +1 -1
  150. package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts +2 -2
  151. package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts.map +1 -1
  152. package/lib/typescript/social/components/legacy/LoadingVideo/index.d.ts.map +1 -1
  153. package/lib/typescript/social/elements/PostMediaElement/PostMediaElement.d.ts.map +1 -1
  154. package/lib/typescript/social/elements/PostMediaElement/styles.d.ts +4 -0
  155. package/lib/typescript/social/elements/PostMediaElement/styles.d.ts.map +1 -1
  156. package/lib/typescript/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.d.ts.map +1 -1
  157. package/lib/typescript/social/features/feed/components/NewsFeed/NewsFeed.d.ts.map +1 -1
  158. package/lib/typescript/social/features/post/Composer/PostComposer.d.ts.map +1 -1
  159. package/lib/typescript/social/hooks/index.d.ts +1 -0
  160. package/lib/typescript/social/hooks/index.d.ts.map +1 -1
  161. package/lib/typescript/social/hooks/useCommunities.d.ts +2 -1
  162. package/lib/typescript/social/hooks/useCommunities.d.ts.map +1 -1
  163. package/lib/typescript/social/hooks/useCommunityPermission.d.ts +14 -0
  164. package/lib/typescript/social/hooks/useCommunityPermission.d.ts.map +1 -0
  165. package/lib/typescript/social/hooks/useCustomRankingGlobalFeed.d.ts.map +1 -1
  166. package/lib/typescript/social/hooks/useFile.d.ts +11 -0
  167. package/lib/typescript/social/hooks/useFile.d.ts.map +1 -1
  168. package/lib/typescript/social/screens/CommunityProfile/components/Header/Header.d.ts.map +1 -1
  169. package/lib/typescript/social/utils/mediaAttachments.d.ts +91 -0
  170. package/lib/typescript/social/utils/mediaAttachments.d.ts.map +1 -0
  171. package/lib/typescript/social/utils/postChildren.d.ts +38 -0
  172. package/lib/typescript/social/utils/postChildren.d.ts.map +1 -0
  173. package/package.json +3 -3
  174. package/src/core/assets/icons/post.tsx +1 -1
  175. package/src/core/assets/icons/story.tsx +1 -1
  176. package/src/core/assets/icons/xml.ts +15 -0
  177. package/src/core/legacy/file.tsx +169 -84
  178. package/src/core/stores/slices/feedSlice.ts +3 -0
  179. package/src/core/stores/slices/globalfeedSlice.ts +5 -0
  180. package/src/core/stores/slices/localVideoThumbnailSlice.ts +58 -0
  181. package/src/core/stores/slices/uiSlice.ts +8 -0
  182. package/src/core/stores/store/index.ts +2 -0
  183. package/src/core/types/mention.ts +34 -0
  184. package/src/social/components/LoadingImage/index.tsx +81 -12
  185. package/src/social/components/LoadingImage/styles.ts +8 -3
  186. package/src/social/components/LoadingVideo/index.tsx +111 -21
  187. package/src/social/components/LoadingVideo/styles.ts +13 -4
  188. package/src/social/components/MediaViewer/MediaViewer.tsx +11 -12
  189. package/src/social/components/MediaViewer/styles.ts +9 -1
  190. package/src/social/components/PostContent/index.tsx +3 -35
  191. package/src/social/components/SelectedMediaComponent/SelectedMediaComponent.tsx +103 -13
  192. package/src/social/components/legacy/LoadingVideo/index.tsx +26 -15
  193. package/src/social/elements/PostMediaElement/PostMediaElement.tsx +139 -29
  194. package/src/social/elements/PostMediaElement/styles.ts +7 -0
  195. package/src/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.tsx +11 -6
  196. package/src/social/features/feed/components/NewsFeed/NewsFeed.tsx +16 -1
  197. package/src/social/features/post/Composer/PostComposer.tsx +304 -91
  198. package/src/social/hooks/index.ts +1 -0
  199. package/src/social/hooks/useCommunities.ts +4 -2
  200. package/src/social/hooks/useCommunityPermission.ts +40 -0
  201. package/src/social/hooks/useCustomRankingGlobalFeed.ts +8 -1
  202. package/src/social/hooks/useFile.ts +11 -0
  203. package/src/social/screens/CommunityProfile/components/Header/Header.tsx +19 -10
  204. package/src/social/utils/mediaAttachments.ts +158 -0
  205. package/src/social/utils/postChildren.ts +85 -0
@@ -1,5 +1,5 @@
1
1
  import { Alert, Keyboard, KeyboardAvoidingView, Platform, ScrollView, Text, TouchableOpacity, View, StatusBar, Linking } from 'react-native';
2
- import { memo, useCallback, useEffect, useState } from 'react';
2
+ import { memo, useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import { ComponentID, ElementID, ImageSizeState, PageID, mediaAttachment } from '../../../enums';
4
4
  import { useAmityPage, useRequestPermission, useUser, isModerator } from '../../../hooks';
5
5
  import { useStyles } from './styles';
@@ -7,21 +7,21 @@ import { AmityPostComposerMode } from '../../../types';
7
7
  import CloseButtonIconElement from '../../../elements/CloseButtonIconElement/CloseButtonIconElement';
8
8
  import { useNavigation } from '@react-navigation/native';
9
9
  import uiSlice from '../../../../core/stores/slices/uiSlice';
10
+ import localVideoThumbnailSlice from '../../../../core/stores/slices/localVideoThumbnailSlice';
10
11
  import { amityPostsFormatter } from '../../../../core/utils/post';
11
12
  import useAuth from '../../../../core/hooks/useAuth';
12
13
  import globalfeedSlice from '../../../../core/stores/slices/globalfeedSlice';
13
14
  import { createPostToFeed, editPost } from '../../../../core/legacy/feed';
14
- import { usePostByIds } from '../../../../core/hooks/usePostByIds';
15
15
  import TextKeyElement from '../../../elements/TextKeyElement/TextKeyElement';
16
16
  import AmityMediaAttachmentComponent from '../components/MediaAttachment';
17
17
  import AmityDetailedMediaAttachmentComponent from '../components/DetailedMediaAttachment';
18
18
  import { useKeyboardStatus } from '../../../hooks';
19
19
  import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
20
20
  import { SelectedMediaComponent } from '../../../components/SelectedMediaComponent';
21
- import { CommunityRepository, PostRepository, UserRepository } from '@amityco/ts-sdk-react-native';
22
- import { useFile } from '../../../hooks';
21
+ import { CommunityRepository, FileRepository, PostRepository, UserRepository } from '@amityco/ts-sdk-react-native';
23
22
  import useMention from '../../../hooks/useMention';
24
23
  import { getPostErrorMessage } from '../../../utils/errors';
24
+ import { MAX_MEDIA_ATTACHMENTS, appendWithinCap, applyFinishedUpload, dedupeMediaPicks, toLocalVideoThumbnails, toggleUploadingSource } from '../../../utils/mediaAttachments';
25
25
  import { ALERT, MAX_MENTION_USERS, MAXIMUM_POST_CHARACTERS } from '../../../../core/constants';
26
26
  import { replaceTriggerValues } from 'react-native-controlled-mentions';
27
27
  import { useUIKitDispatch } from '../../../../core/stores/store';
@@ -34,7 +34,7 @@ const AmityPostComposerPage = ({
34
34
  community,
35
35
  post
36
36
  }) => {
37
- var _post$data;
37
+ var _post$data, _post$children;
38
38
  useRequestPermission({
39
39
  onRequestPermissionFailed: () => {
40
40
  Linking.openSettings();
@@ -53,9 +53,6 @@ const AmityPostComposerPage = ({
53
53
  pageId
54
54
  });
55
55
  const styles = useStyles(themeStyles);
56
- const {
57
- getImage
58
- } = useFile();
59
56
  const isEditMode = mode === AmityPostComposerMode.EDIT;
60
57
  const navigation = useNavigation();
61
58
  const {
@@ -76,6 +73,9 @@ const AmityPostComposerPage = ({
76
73
  showToastMessage,
77
74
  hideToastMessage
78
75
  } = uiSlice.actions;
76
+ const {
77
+ setLocalVideoThumbnails
78
+ } = localVideoThumbnailSlice.actions;
79
79
  const [inputMessage, setInputMessage] = useState((post === null || post === void 0 || (_post$data = post.data) === null || _post$data === void 0 ? void 0 : _post$data.text) ?? '');
80
80
  const [mentionsPosition, setMentionsPosition] = useState([]);
81
81
  const [chosenMediaType, setChosenMediaType] = useState(null);
@@ -84,7 +84,14 @@ const AmityPostComposerPage = ({
84
84
  const [mentionUsers, setMentionUsers] = useState([]);
85
85
  const [isSwipeup, setIsSwipeup] = useState(true);
86
86
  const [deletedPostIds, setDeletedPostIds] = useState([]);
87
- const [isUploading, setIsUploading] = useState(false);
87
+ // Upload progress is tracked per media item, keyed by the item's `source`
88
+ // url — the same shape as `imageErrors`/`videoErrors` below. A
89
+ // single shared boolean was flipped back to false by whichever upload
90
+ // finished first, so Post/Save unlocked while the remaining frames were
91
+ // still in flight. Sources are unique across images and videos, so one set
92
+ // covers both attachment types.
93
+ const [uploadingSources, setUploadingSources] = useState(new Set());
94
+ const isUploading = uploadingSources.size > 0;
88
95
  const [hasChangedAttachment, setHasChangedAttachment] = useState(false);
89
96
  const [imageErrors, setImageErrors] = useState(new Set());
90
97
  const [videoErrors, setVideoErrors] = useState(new Set());
@@ -111,7 +118,7 @@ const AmityPostComposerPage = ({
111
118
  const effectiveCommunity = community ?? fetchedCommunity;
112
119
  const privateCommunityId = !(effectiveCommunity !== null && effectiveCommunity !== void 0 && effectiveCommunity.isPublic) && (effectiveCommunity === null || effectiveCommunity === void 0 ? void 0 : effectiveCommunity.communityId);
113
120
  const title = isEditMode ? 'Edit Post' : (effectiveCommunity === null || effectiveCommunity === void 0 ? void 0 : effectiveCommunity.displayName) ?? 'My Timeline';
114
- const isInputValid = !isUploading && imageErrors.size === 0 && videoErrors.size === 0 && inputMessage.trim().length <= MAXIMUM_POST_CHARACTERS && (inputMessage.trim().length > 0 || displayImages.length > 0 || displayVideos.length > 0) && (displayImages.length <= 10 || displayVideos.length <= 10);
121
+ const isInputValid = !isUploading && imageErrors.size === 0 && videoErrors.size === 0 && inputMessage.trim().length <= MAXIMUM_POST_CHARACTERS && (inputMessage.trim().length > 0 || displayImages.length > 0 || displayVideos.length > 0) && (displayImages.length <= MAX_MEDIA_ATTACHMENTS || displayVideos.length <= MAX_MEDIA_ATTACHMENTS);
115
122
  const {
116
123
  renderInput,
117
124
  renderSuggestions
@@ -132,8 +139,26 @@ const AmityPostComposerPage = ({
132
139
  }]);
133
140
  }
134
141
  });
142
+
143
+ // The edited post carries its own hydrated children: every post payload
144
+ // ingests `postChildren` into the `post` cache (PAYLOAD2MODEL maps it to
145
+ // 'post'), and postLinkedObject reads them straight back out. So the media is
146
+ // known on the first frame, with no fetch — no second source to reconcile.
147
+ const editChildren = (post === null || post === void 0 ? void 0 : post.childrenPosts) ?? [];
148
+
149
+ // `childrenPosts` drops cache misses silently (postLinkedObject filters with
150
+ // isNonNullable), so a short array means we cannot see every child. There is
151
+ // no fallback fetch behind it, so treat that state as read-only: no
152
+ // attachment bar, no Save.
153
+ const editChildrenIncomplete = isEditMode && editChildren.length !== ((post === null || post === void 0 || (_post$children = post.children) === null || _post$children === void 0 ? void 0 : _post$children.length) ?? 0);
135
154
  const checkIsEditValid = () => {
136
155
  var _post$data2;
156
+ // `type` in onPressPost is derived from displayImages/displayVideos, so
157
+ // saving while a child is missing submits it as removed — `attachments: []`
158
+ // and type 'text' strips the post's media outright. Nothing the user can do
159
+ // in the composer makes an unrendered child safe to save, so key this on
160
+ // completeness alone rather than on hasChangedAttachment.
161
+ if (editChildrenIncomplete) return false;
137
162
  return isInputValid && (inputMessage !== (post === null || post === void 0 || (_post$data2 = post.data) === null || _post$data2 === void 0 ? void 0 : _post$data2.text) || hasChangedAttachment);
138
163
  };
139
164
  const parsePostText = useCallback((text, mentionUsersArr) => {
@@ -145,48 +170,81 @@ const AmityPostComposerPage = ({
145
170
  return parsedText;
146
171
  }, []);
147
172
 
148
- // Load the edited post's child media the same way the web UIKit does
149
- // (usePostByIds → batch getPostByIds), instead of the racy per-id fetch.
150
- const childrenPosts = usePostByIds((post === null || post === void 0 ? void 0 : post.children) ?? []);
173
+ // A child post's file is usually already in the SDK cache, where the linked
174
+ // object exposes it synchronously (getImageInfo / getVideoInfo /
175
+ // getVideoThumbnailInfo are `pullFromCache(['file','get', …])` over the very
176
+ // ids we'd otherwise refetch). Use the cached `fileUrl` when it's there and
177
+ // fall back to the round trip on a cache miss. `fileUrlWithSize` is just
178
+ // `?size=`, so both branches produce the same url.
179
+ const resolveFileUrl = useCallback(async (fileUrl, fileId) => {
180
+ var _file$data;
181
+ if (fileUrl) return FileRepository.fileUrlWithSize(fileUrl, ImageSizeState.full);
182
+ if (!fileId) return undefined;
183
+
184
+ // Resolve the file directly rather than through useFile: that hook
185
+ // answers a miss with the default *avatar* image, which is right for a
186
+ // profile picture and wrong for post media. A video whose thumbnail the
187
+ // server has not produced yet has no file to resolve, and handing back an
188
+ // avatar made the carousel render a person icon behind the play button as
189
+ // though it were the real frame — and, because the url looked valid, it
190
+ // was also stored as the local thumbnail the feed falls back on.
191
+ const file = await FileRepository.getFile(fileId);
192
+ const resolved = file === null || file === void 0 || (_file$data = file.data) === null || _file$data === void 0 ? void 0 : _file$data.fileUrl;
193
+ return resolved ? FileRepository.fileUrlWithSize(resolved, ImageSizeState.full) : undefined;
194
+ }, []);
151
195
  const getPostInfo = useCallback(async response => {
152
196
  try {
153
197
  const images = [];
154
198
  const videos = [];
155
199
  for (const item of response) {
156
200
  if ((item === null || item === void 0 ? void 0 : item.dataType) === 'image') {
157
- var _data;
201
+ var _data, _item$getImageInfo, _imageInfo$getWidth, _imageInfo$getHeight;
158
202
  const fileId = item === null || item === void 0 || (_data = item.data) === null || _data === void 0 ? void 0 : _data.fileId;
159
- const url = await getImage({
160
- fileId: fileId,
161
- imageSize: ImageSizeState.full
162
- });
203
+ // Dimensions for composer frame-ratio classification (REQ-003d).
204
+ const imageInfo = item === null || item === void 0 || (_item$getImageInfo = item.getImageInfo) === null || _item$getImageInfo === void 0 ? void 0 : _item$getImageInfo.call(item);
205
+ const url = await resolveFileUrl(imageInfo === null || imageInfo === void 0 ? void 0 : imageInfo.fileUrl, fileId);
163
206
  images.push({
164
207
  url,
165
208
  fileId,
166
209
  fileName: fileId,
210
+ // A hydrated child has no local pick behind it, so its identity
211
+ // is the file it already is. Unique per child, stable for the
212
+ // whole session, and never equal to a picker file name — so a
213
+ // later pick is de-duplicated against the other staged picks
214
+ // only, which is the most the picker can tell us.
215
+ localId: fileId ?? item.postId,
167
216
  isUploaded: true,
168
- postId: item.postId
217
+ postId: item.postId,
218
+ width: imageInfo === null || imageInfo === void 0 || (_imageInfo$getWidth = imageInfo.getWidth) === null || _imageInfo$getWidth === void 0 ? void 0 : _imageInfo$getWidth.call(imageInfo),
219
+ height: imageInfo === null || imageInfo === void 0 || (_imageInfo$getHeight = imageInfo.getHeight) === null || _imageInfo$getHeight === void 0 ? void 0 : _imageInfo$getHeight.call(imageInfo)
169
220
  });
170
221
  } else if ((item === null || item === void 0 ? void 0 : item.dataType) === 'video') {
171
- var _videoData$videoFileI, _fileUrls$, _fileUrls$2;
222
+ var _videoData$videoFileI, _item$getVideoInfo, _item$getVideoThumbna, _videoInfo$getWidth, _videoInfo$getHeight, _videoInfo$getRotatio;
172
223
  const videoData = item === null || item === void 0 ? void 0 : item.data;
173
224
  const fileId = videoData === null || videoData === void 0 || (_videoData$videoFileI = videoData.videoFileId) === null || _videoData$videoFileI === void 0 ? void 0 : _videoData$videoFileI.original;
174
225
  const thumbnailFileId = videoData === null || videoData === void 0 ? void 0 : videoData.thumbnailFileId;
175
- const fileUrls = await Promise.allSettled([fileId, thumbnailFileId].map(async id => {
176
- return await getImage({
177
- fileId: id,
178
- imageSize: ImageSizeState.full
179
- });
180
- }));
226
+ // STORED dims + rotation; rotation is applied at classify time
227
+ // (SelectedMediaComponent REQ-003d1 / AmityVideo REQ-SDK-002).
228
+ const videoInfo = item === null || item === void 0 || (_item$getVideoInfo = item.getVideoInfo) === null || _item$getVideoInfo === void 0 ? void 0 : _item$getVideoInfo.call(item);
229
+ const thumbnailInfo = item === null || item === void 0 || (_item$getVideoThumbna = item.getVideoThumbnailInfo) === null || _item$getVideoThumbna === void 0 ? void 0 : _item$getVideoThumbna.call(item);
230
+ // Settle the two urls independently: a missing or unreachable
231
+ // thumbnail must not cost us the video itself. `Promise.all` would
232
+ // reject the pair, abort this loop, and leave displayVideos empty
233
+ // — which now also keeps Save disabled (see checkIsEditValid).
234
+ const [urlResult, thumbNailResult] = await Promise.allSettled([resolveFileUrl(videoInfo === null || videoInfo === void 0 ? void 0 : videoInfo.fileUrl, fileId), resolveFileUrl(thumbnailInfo === null || thumbnailInfo === void 0 ? void 0 : thumbnailInfo.fileUrl, thumbnailFileId)]);
235
+ const url = urlResult.status === 'fulfilled' ? urlResult.value : undefined;
236
+ const thumbNail = thumbNailResult.status === 'fulfilled' ? thumbNailResult.value : undefined;
181
237
  videos.push({
182
- //@ts-ignore
183
- url: (_fileUrls$ = fileUrls[0]) === null || _fileUrls$ === void 0 ? void 0 : _fileUrls$.value,
238
+ url,
184
239
  fileId: fileId,
185
240
  fileName: fileId,
241
+ localId: fileId ?? item.postId,
186
242
  isUploaded: true,
187
- //@ts-ignore
188
- thumbNail: (_fileUrls$2 = fileUrls[1]) === null || _fileUrls$2 === void 0 ? void 0 : _fileUrls$2.value,
189
- postId: item.postId
243
+ thumbNail,
244
+ postId: item.postId,
245
+ width: videoInfo === null || videoInfo === void 0 || (_videoInfo$getWidth = videoInfo.getWidth) === null || _videoInfo$getWidth === void 0 ? void 0 : _videoInfo$getWidth.call(videoInfo),
246
+ height: videoInfo === null || videoInfo === void 0 || (_videoInfo$getHeight = videoInfo.getHeight) === null || _videoInfo$getHeight === void 0 ? void 0 : _videoInfo$getHeight.call(videoInfo),
247
+ rotation: videoInfo === null || videoInfo === void 0 || (_videoInfo$getRotatio = videoInfo.getRotation) === null || _videoInfo$getRotatio === void 0 ? void 0 : _videoInfo$getRotatio.call(videoInfo)
190
248
  });
191
249
  }
192
250
  }
@@ -199,14 +257,36 @@ const AmityPostComposerPage = ({
199
257
  } catch (error) {
200
258
  console.log('error: ', error);
201
259
  }
202
- }, [getImage]);
260
+ }, [resolveFileUrl]);
203
261
  useEffect(() => {
204
262
  setDeletedPostIds([]);
205
263
  return () => setDeletedPostIds([]);
206
264
  }, []);
265
+
266
+ // `post` comes from route params, so `editChildren` is reference-stable and
267
+ // this hydrates once. The hasChangedAttachment guard is belt-and-braces:
268
+ // getPostInfo assigns displayImages/displayVideos wholesale, so a re-run must
269
+ // never be able to wipe media the user added.
207
270
  useEffect(() => {
208
- if (childrenPosts.length > 0) getPostInfo(childrenPosts);
209
- }, [getPostInfo, childrenPosts]);
271
+ if (editChildren.length === 0 || hasChangedAttachment) return;
272
+ getPostInfo(editChildren);
273
+ }, [getPostInfo, post === null || post === void 0 ? void 0 : post.childrenPosts, hasChangedAttachment]);
274
+
275
+ // Media type of the post being edited, read off the children's dataType —
276
+ // the same signal the web UIKit feeds into useMediaAttachmentVisible.
277
+ const editMediaType = useMemo(() => {
278
+ if (editChildren.some(item => (item === null || item === void 0 ? void 0 : item.dataType) === 'image')) return mediaAttachment.image;
279
+ if (editChildren.some(item => (item === null || item === void 0 ? void 0 : item.dataType) === 'video')) return mediaAttachment.video;
280
+ return null;
281
+ }, [post === null || post === void 0 ? void 0 : post.childrenPosts]);
282
+
283
+ // The post's single media type, as every media entry point must see it: the
284
+ // staged attachments when there are any, otherwise the type the edited post
285
+ // already has. Read the display arrays rather than `chosenMediaType`, which
286
+ // is assigned in an effect and so trails them by a render. Once the user has
287
+ // cleared the attachments itself the post has no type again and all three
288
+ // buttons come back, matching web.
289
+ const activeMediaType = displayImages.length > 0 ? mediaAttachment.image : displayVideos.length > 0 ? mediaAttachment.video : hasChangedAttachment ? null : editMediaType;
210
290
  const getMentionPositions = useCallback((text, mentioneeIds) => {
211
291
  let index = 0;
212
292
  let mentions = [];
@@ -320,6 +400,21 @@ const AmityPostComposerPage = ({
320
400
  return;
321
401
  }
322
402
  dispatch(hideToastMessage());
403
+ // The server has no thumbnail for these videos until it finishes
404
+ // transcoding, so hand the feed the frames the composer already decoded
405
+ // to bridge that window (PDT-4904, web parity: LayoutProvider
406
+ // videoThumbnail). Keyed by the uploaded original video's fileId, which
407
+ // is what the created post exposes as `data.videoFileId.original`.
408
+ if (type === 'video' && displayVideos.length > 0) {
409
+ dispatch(setLocalVideoThumbnails({
410
+ postId: response.postId,
411
+ // Only frames that actually decoded: an entry with no thumbnail
412
+ // would still satisfy the feed's lookup and suppress its own
413
+ // pending state, leaving the frame blank instead of showing the
414
+ // play affordance over a plain background.
415
+ videos: toLocalVideoThumbnails(displayVideos)
416
+ }));
417
+ }
323
418
  if (targetType === 'community' && ((effectiveCommunity === null || effectiveCommunity === void 0 ? void 0 : effectiveCommunity.postSetting) === 'ADMIN_REVIEW_POST_REQUIRED' || effectiveCommunity !== null && effectiveCommunity !== void 0 && effectiveCommunity.needApprovalOnPostCreation) && !isCommunityModerator) {
324
419
  onPressClose();
325
420
  return Alert.alert('Post submitted', 'Your post has been submitted to the pending list. It will be reviewed by community moderator', [{
@@ -350,7 +445,7 @@ const AmityPostComposerPage = ({
350
445
  toastMessage: errorMessage
351
446
  }));
352
447
  }
353
- }, [addPostToGlobalFeed, chosenMediaType, effectiveCommunity, deletedPostIds, dispatch, displayImages, displayVideos, hideToastMessage, inputMessage, isEditMode, isInputValid, isCommunityModerator, mentionUsers, mentionsPosition, onPressClose, post, showToastMessage, targetId, targetType, updateByPostId]);
448
+ }, [addPostToGlobalFeed, chosenMediaType, effectiveCommunity, deletedPostIds, dispatch, displayImages, displayVideos, hideToastMessage, inputMessage, isEditMode, isInputValid, isCommunityModerator, mentionUsers, mentionsPosition, onPressClose, setLocalVideoThumbnails, post, showToastMessage, targetId, targetType, updateByPostId]);
354
449
  let tEvents = [];
355
450
  const onSwipe = useCallback(touchEvent => {
356
451
  const swipeUp = touchEvent[0] > touchEvent[touchEvent.length - 1];
@@ -372,6 +467,14 @@ const AmityPostComposerPage = ({
372
467
  return {
373
468
  url: url,
374
469
  fileName: fileName,
470
+ // Camera captures land on a freshly generated temp path, so the path
471
+ // itself is the identity: unique per shot, and stable once the upload
472
+ // rewrites `url` to the remote one. Deliberately no
473
+ // `localFileName` — two shots of the same scene are two distinct
474
+ // attachments and must never de-duplicate against each other, nor
475
+ // against a library pick that happens to share a basename.
476
+ localId: url,
477
+ localUrl: url,
375
478
  fileId: '',
376
479
  isUploaded: false
377
480
  };
@@ -384,8 +487,8 @@ const AmityPostComposerPage = ({
384
487
  return setChosenMediaType(null);
385
488
  }, [displayImages === null || displayImages === void 0 ? void 0 : displayImages.length, displayVideos === null || displayVideos === void 0 ? void 0 : displayVideos.length]);
386
489
  const pickCamera = useCallback(async mediaType => {
387
- if (mediaType === 'photo' && displayImages.length === 10) return Alert.alert('Maximum upload limit reached', "You've reached the upload limit of 10 images. Any additional images will not be saved.");
388
- if (mediaType === 'video' && displayVideos.length === 10) return Alert.alert('Maximum upload limit reached', "You've reached the upload limit of 10 videos. Any additional videos will not be saved.");
490
+ if (mediaType === 'photo' && displayImages.length >= MAX_MEDIA_ATTACHMENTS) return Alert.alert('Maximum upload limit reached', "You've reached the upload limit of 10 images. Any additional images will not be saved.");
491
+ if (mediaType === 'video' && displayVideos.length >= MAX_MEDIA_ATTACHMENTS) return Alert.alert('Maximum upload limit reached', "You've reached the upload limit of 10 videos. Any additional videos will not be saved.");
389
492
  try {
390
493
  const result = await launchCamera({
391
494
  mediaType: mediaType,
@@ -414,8 +517,13 @@ const AmityPostComposerPage = ({
414
517
  }
415
518
  }, [displayImages.length, displayVideos.length, processMedia]);
416
519
  const onPressCamera = useCallback(async () => {
417
- if (displayImages.length > 0) return pickCamera('photo');
418
- if (displayVideos.length > 0) return pickCamera('video');
520
+ // The camera button is always rendered — unlike photo/video, neither
521
+ // attachment bar hides it on `chosenMediaType` — so this is the one entry
522
+ // point that must decide the capture type itself. Keying it on the staged
523
+ // arrays alone offered both types while an edited post's media was still
524
+ // hydrating, which is how a video could be shot onto an image post.
525
+ if (activeMediaType === mediaAttachment.image) return pickCamera('photo');
526
+ if (activeMediaType === mediaAttachment.video) return pickCamera('video');
419
527
  if (Platform.OS === 'ios') return pickCamera('mixed');
420
528
  Alert.alert('Open Camera', null, [{
421
529
  text: 'Photo',
@@ -424,46 +532,95 @@ const AmityPostComposerPage = ({
424
532
  text: 'Video',
425
533
  onPress: async () => pickCamera('video')
426
534
  }]);
427
- }, [displayImages.length, displayVideos.length, pickCamera]);
535
+ }, [activeMediaType, pickCamera]);
428
536
 
429
537
  // Open the native OS camera roll (matches the web UIKit's native file
430
538
  // picker). Appends the picked media, de-duplicating by uri and capping at 10.
431
539
  const pickFromLibrary = useCallback(async mediaType => {
432
540
  var _result$assets;
433
541
  const isPhoto = mediaType === 'photo';
542
+ // A post holds one media type. The bar hides the mismatched button, but
543
+ // guard the entry point too so a tap landing before the button updates
544
+ // can't replace the existing attachments with the other type.
545
+ const pickedType = isPhoto ? mediaAttachment.image : mediaAttachment.video;
546
+ if (activeMediaType && activeMediaType !== pickedType) return;
434
547
  const current = isPhoto ? displayImages.length : displayVideos.length;
435
- if (current >= 10) return Alert.alert('Maximum upload limit reached', `You've reached the upload limit of 10 ${isPhoto ? 'images' : 'videos'}. Any additional ${isPhoto ? 'images' : 'videos'} will not be saved.`);
548
+ if (current >= MAX_MEDIA_ATTACHMENTS) return Alert.alert('Maximum upload limit reached', `You've reached the upload limit of 10 ${isPhoto ? 'images' : 'videos'}. Any additional ${isPhoto ? 'images' : 'videos'} will not be saved.`);
436
549
  const result = await launchImageLibrary({
437
550
  mediaType,
438
551
  quality: 1,
439
- selectionLimit: 10 - current
552
+ selectionLimit: MAX_MEDIA_ATTACHMENTS - current,
553
+ // Required for `asset.id`, which is what recognises a re-pick of an
554
+ // asset already staged. Without it the iOS picker builds its
555
+ // PHPickerConfiguration without a photo library, so results carry no
556
+ // assetIdentifier and the native side never resolves a PHAsset —
557
+ // leaving `id` undefined and the fallback key, `fileName`, a fresh
558
+ // UUID per pick. Both dedupe keys were therefore unique every time
559
+ // and picking the same photo twice staged it twice. Android was
560
+ // unaffected: its filename is the MediaStore display name, stable
561
+ // across picks.
562
+ includeExtra: true
440
563
  });
441
564
  if (result.didCancel || !((_result$assets = result.assets) !== null && _result$assets !== void 0 && _result$assets.length)) return;
442
- const setter = isPhoto ? setDisplayImages : setDisplayVideos;
443
- setter(prev => {
444
- // Silently drop duplicates, matching the web UIKit (which dedups by
445
- // file name). The picker copies each pick to a fresh temp uri, so uri
446
- // is not stable across picks — the original `fileName` is.
447
- const seen = new Set(prev.map(m => m.fileName));
448
- const additions = [];
449
- for (const asset of result.assets ?? []) {
450
- if (!asset.uri) continue;
451
- const fileName = asset.fileName ?? asset.uri.substring(asset.uri.lastIndexOf('/') + 1);
452
- if (seen.has(fileName)) continue; // duplicate → silently drop
453
- seen.add(fileName);
454
- additions.push({
455
- url: asset.uri,
456
- fileName,
457
- fileId: '',
458
- isUploaded: false
459
- });
460
- }
461
- const updated = [...prev, ...additions];
462
- return updated.length > 10 ? updated.slice(0, 10) : updated;
565
+
566
+ // iOS video is the one pick whose dims cannot be trusted: the picker
567
+ // reports the track's stored `naturalSize` with no rotation alongside
568
+ // it, so a portrait iPhone clip arrives as 1920×1080 and would
569
+ // classify the frame 16:9 (PDT-4904). Images on both platforms, and
570
+ // Android video (its extractor swaps w/h on 90°/270°), are already
571
+ // display-oriented and unaffected. With no rotation to correct the iOS
572
+ // value by, carry nothing and let SelectedMediaComponent measure the
573
+ // generated thumbnail, which is a rendered frame.
574
+ const isIosVideoDimsUnusable = !isPhoto && Platform.OS === 'ios';
575
+ const candidates = (result.assets ?? []).flatMap(asset => {
576
+ if (!asset.uri) return [];
577
+ const fileName = asset.fileName ?? asset.uri.substring(asset.uri.lastIndexOf('/') + 1);
578
+ return [{
579
+ url: asset.uri,
580
+ fileName,
581
+ // `asset.id` is the library's own identifier for the asset (PHAsset
582
+ // localIdentifier / MediaStore id) and is the one key that survives
583
+ // the picker copying the pick to a new temp uri every time. It is
584
+ // not populated by every picker configuration, so fall back to the
585
+ // file name and keep the name as a second key either way — that is
586
+ // what catches a re-pick when no id came back.
587
+ localId: asset.id ?? fileName,
588
+ localUrl: asset.uri,
589
+ localFileName: fileName,
590
+ fileId: '',
591
+ isUploaded: false,
592
+ width: isIosVideoDimsUnusable ? undefined : asset.width,
593
+ height: isIosVideoDimsUnusable ? undefined : asset.height
594
+ }];
463
595
  });
464
- }, [displayImages.length, displayVideos.length]);
596
+
597
+ // Stage the picks and claim their upload slots in one go. Staging alone
598
+ // used to flip `isInputValid` true on the very next render — the frames
599
+ // exist, and `uploadingSources` is still empty because each child only
600
+ // registers from its own mount effect — so Post sat enabled for ~940 ms
601
+ // with nothing uploaded and a quick tap posted media that had no fileId
602
+ // yet. Seeding here makes "staged" and "gated" the same update; children
603
+ // still clear their own entry when they finish, and the unmount cleanup
604
+ // still covers a frame removed mid-flight.
605
+ const staged = isPhoto ? displayImages : displayVideos;
606
+ const accepted = appendWithinCap(staged, dedupeMediaPicks(staged, candidates));
607
+ // Only the entries that actually survived dedupe and the cap — seeding a
608
+ // key no child will ever mount for would leave Post disabled forever.
609
+ const newlyStaged = accepted.slice(staged.length);
610
+ const setter = isPhoto ? setDisplayImages : setDisplayVideos;
611
+ setter(accepted);
612
+ setUploadingSources(prev => newlyStaged.reduce((acc, item) => toggleUploadingSource(acc, item.url, true), prev));
613
+ }, [activeMediaType, displayImages, displayVideos]);
465
614
  const onPressImage = useCallback(() => pickFromLibrary('photo'), [pickFromLibrary]);
466
615
  const onPressVideo = useCallback(() => pickFromLibrary('video'), [pickFromLibrary]);
616
+
617
+ // Children report their own upload start/end keyed by `source`, and a child
618
+ // that unmounts mid-upload (frame removed while still uploading) clears its
619
+ // own entry from its cleanup — otherwise the set would never empty and Post
620
+ // would stay disabled forever.
621
+ const handleUploadingChange = useCallback((uploading, source) => {
622
+ setUploadingSources(prev => toggleUploadingSource(prev, source, uploading));
623
+ }, []);
467
624
  const handleImageUploadError = useCallback((hasError, source) => {
468
625
  setImageErrors(prev => {
469
626
  const newSet = new Set(prev);
@@ -488,7 +645,7 @@ const AmityPostComposerPage = ({
488
645
  }, []);
489
646
  const handleOnCloseImage = useCallback((originalPath, _, postId) => {
490
647
  setHasChangedAttachment(true);
491
- setDeletedPostIds(prev => [...prev, postId]);
648
+ if (postId) setDeletedPostIds(prev => [...prev, postId]);
492
649
  setImageErrors(prev => {
493
650
  const newSet = new Set(prev);
494
651
  newSet.delete(originalPath);
@@ -501,7 +658,7 @@ const AmityPostComposerPage = ({
501
658
  }, []);
502
659
  const handleOnCloseVideo = useCallback((originalPath, _, postId) => {
503
660
  setHasChangedAttachment(true);
504
- setDeletedPostIds(prev => [...prev, postId]);
661
+ if (postId) setDeletedPostIds(prev => [...prev, postId]);
505
662
  setVideoErrors(prev => {
506
663
  const newSet = new Set(prev);
507
664
  newSet.delete(originalPath);
@@ -512,47 +669,37 @@ const AmityPostComposerPage = ({
512
669
  return newData;
513
670
  });
514
671
  }, []);
515
- const handleOnFinishImage = useCallback((fileId, fileUrl, fileName, index) => {
672
+ const handleOnFinishImage = useCallback((fileId, fileUrl, fileName, _index, originalPath) => {
516
673
  setHasChangedAttachment(true);
517
- const imageObject = {
674
+ setDisplayImages(prevData => applyFinishedUpload(prevData, originalPath, {
518
675
  url: fileUrl,
519
676
  fileId: fileId,
520
677
  fileName: fileName,
521
678
  isUploaded: true
522
- };
523
- setDisplayImages(prevData => {
524
- const newData = [...prevData];
525
- newData[index] = imageObject;
526
- return newData;
527
- });
679
+ }));
528
680
  }, []);
529
- const handleOnFinishVideo = useCallback((fileId, fileUrl, fileName, index, _, thumbnail) => {
681
+ const handleOnFinishVideo = useCallback((fileId, fileUrl, fileName, _index, originalPath, thumbnail) => {
530
682
  setHasChangedAttachment(true);
531
- const imageObject = {
683
+ setDisplayVideos(prevData => applyFinishedUpload(prevData, originalPath, {
532
684
  url: fileUrl,
533
685
  fileId: fileId,
534
686
  fileName: fileName,
535
687
  isUploaded: true,
536
688
  thumbNail: thumbnail
537
- };
538
- setDisplayVideos(prevData => {
539
- const newData = [...prevData];
540
- newData[index] = imageObject;
541
- return newData;
542
- });
689
+ }));
543
690
  }, []);
544
691
 
545
692
  // PDT-4310 / PDT-4312: at the 10-attachment cap the camera + gallery icons
546
693
  // are disabled until the user removes a frame.
547
- const isMediaCapReached = displayImages.length >= 10 || displayVideos.length >= 10;
694
+ const isMediaCapReached = displayImages.length >= MAX_MEDIA_ATTACHMENTS || displayVideos.length >= MAX_MEDIA_ATTACHMENTS;
548
695
  const renderDetailedAttachment = useCallback(() => {
549
- if (isEditMode) return null;
696
+ if (editChildrenIncomplete) return null;
550
697
  if (shouldShowDetailAttachment) {
551
698
  return /*#__PURE__*/React.createElement(AmityDetailedMediaAttachmentComponent, {
552
699
  onPressCamera: onPressCamera,
553
700
  onPressImage: onPressImage,
554
701
  onPressVideo: onPressVideo,
555
- chosenMediaType: chosenMediaType,
702
+ chosenMediaType: activeMediaType,
556
703
  onHeightChange: setAttachmentBarHeight,
557
704
  disabled: isMediaCapReached
558
705
  });
@@ -561,11 +708,11 @@ const AmityPostComposerPage = ({
561
708
  onPressCamera: onPressCamera,
562
709
  onPressImage: onPressImage,
563
710
  onPressVideo: onPressVideo,
564
- chosenMediaType: chosenMediaType,
711
+ chosenMediaType: activeMediaType,
565
712
  onHeightChange: setAttachmentBarHeight,
566
713
  disabled: isMediaCapReached
567
714
  });
568
- }, [chosenMediaType, isEditMode, isMediaCapReached, onPressCamera, onPressImage, onPressVideo, shouldShowDetailAttachment]);
715
+ }, [activeMediaType, editChildrenIncomplete, isMediaCapReached, onPressCamera, onPressImage, onPressVideo, shouldShowDetailAttachment]);
569
716
  if (isExcluded) return null;
570
717
  return /*#__PURE__*/React.createElement(SafeAreaView, {
571
718
  testID: accessibilityId,
@@ -612,7 +759,7 @@ const AmityPostComposerPage = ({
612
759
  onLoadFinish: handleOnFinishImage,
613
760
  onUploadError: handleImageUploadError,
614
761
  isEditMode: isEditMode,
615
- setIsUploading: setIsUploading
762
+ onUploadingChange: handleUploadingChange
616
763
  }), displayVideos.length > 0 && /*#__PURE__*/React.createElement(SelectedMediaComponent, {
617
764
  mediaType: "video",
618
765
  media: displayVideos,
@@ -620,7 +767,7 @@ const AmityPostComposerPage = ({
620
767
  onLoadFinish: handleOnFinishVideo,
621
768
  onUploadError: handleVideoUploadError,
622
769
  isEditMode: isEditMode,
623
- setIsUploading: setIsUploading
770
+ onUploadingChange: handleUploadingChange
624
771
  }))), renderSuggestions({
625
772
  type: 'post'
626
773
  }), /*#__PURE__*/React.createElement(View, {