@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
@@ -10,7 +10,7 @@ import {
10
10
  StatusBar,
11
11
  Linking,
12
12
  } from 'react-native';
13
- import { FC, memo, useCallback, useEffect, useState } from 'react';
13
+ import { FC, memo, useCallback, useEffect, useMemo, useState } from 'react';
14
14
  import {
15
15
  ComponentID,
16
16
  ElementID,
@@ -34,11 +34,11 @@ import { IDisplayImage, IMentionPosition } from '../../../../core/types';
34
34
  import CloseButtonIconElement from '../../../elements/CloseButtonIconElement/CloseButtonIconElement';
35
35
  import { useNavigation } from '@react-navigation/native';
36
36
  import uiSlice from '../../../../core/stores/slices/uiSlice';
37
+ import localVideoThumbnailSlice from '../../../../core/stores/slices/localVideoThumbnailSlice';
37
38
  import { amityPostsFormatter } from '../../../../core/utils/post';
38
39
  import useAuth from '../../../../core/hooks/useAuth';
39
40
  import globalfeedSlice from '../../../../core/stores/slices/globalfeedSlice';
40
41
  import { createPostToFeed, editPost } from '../../../../core/legacy/feed';
41
- import { usePostByIds } from '../../../../core/hooks/usePostByIds';
42
42
  import TextKeyElement from '../../../elements/TextKeyElement/TextKeyElement';
43
43
  import AmityMediaAttachmentComponent from '../components/MediaAttachment';
44
44
  import AmityDetailedMediaAttachmentComponent from '../components/DetailedMediaAttachment';
@@ -53,12 +53,20 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack';
53
53
  import { RootStackParamList } from '../../../../core/routes/RouteParamList';
54
54
  import {
55
55
  CommunityRepository,
56
+ FileRepository,
56
57
  PostRepository,
57
58
  UserRepository,
58
59
  } from '@amityco/ts-sdk-react-native';
59
- import { useFile } from '../../../hooks';
60
60
  import useMention from '../../../hooks/useMention';
61
61
  import { getPostErrorMessage } from '../../../utils/errors';
62
+ import {
63
+ MAX_MEDIA_ATTACHMENTS,
64
+ appendWithinCap,
65
+ applyFinishedUpload,
66
+ dedupeMediaPicks,
67
+ toLocalVideoThumbnails,
68
+ toggleUploadingSource,
69
+ } from '../../../utils/mediaAttachments';
62
70
  import {
63
71
  ALERT,
64
72
  MAX_MENTION_USERS,
@@ -86,7 +94,6 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
86
94
  const { AmityPostComposerPageBehavior } = useBehaviour();
87
95
  const { isExcluded, themeStyles, accessibilityId } = useAmityPage({ pageId });
88
96
  const styles = useStyles(themeStyles);
89
- const { getImage } = useFile();
90
97
  const isEditMode = mode === AmityPostComposerMode.EDIT;
91
98
  const navigation =
92
99
  useNavigation<NativeStackNavigationProp<RootStackParamList>>();
@@ -99,6 +106,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
99
106
  const currentUser = useUser((client as Amity.Client)?.userId || '');
100
107
  const isCommunityModerator = isModerator(currentUser?.roles);
101
108
  const { showToastMessage, hideToastMessage } = uiSlice.actions;
109
+ const { setLocalVideoThumbnails } = localVideoThumbnailSlice.actions;
102
110
  const [inputMessage, setInputMessage] = useState<string>(
103
111
  (post?.data as Amity.ContentDataText)?.text ?? ''
104
112
  );
@@ -111,7 +119,16 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
111
119
  const [mentionUsers, setMentionUsers] = useState<TSearchItem[]>([]);
112
120
  const [isSwipeup, setIsSwipeup] = useState(true);
113
121
  const [deletedPostIds, setDeletedPostIds] = useState<string[]>([]);
114
- const [isUploading, setIsUploading] = useState(false);
122
+ // Upload progress is tracked per media item, keyed by the item's `source`
123
+ // url — the same shape as `imageErrors`/`videoErrors` below. A
124
+ // single shared boolean was flipped back to false by whichever upload
125
+ // finished first, so Post/Save unlocked while the remaining frames were
126
+ // still in flight. Sources are unique across images and videos, so one set
127
+ // covers both attachment types.
128
+ const [uploadingSources, setUploadingSources] = useState<Set<string>>(
129
+ new Set()
130
+ );
131
+ const isUploading = uploadingSources.size > 0;
115
132
  const [hasChangedAttachment, setHasChangedAttachment] = useState(false);
116
133
  const [imageErrors, setImageErrors] = useState<Set<string>>(new Set());
117
134
  const [videoErrors, setVideoErrors] = useState<Set<string>>(new Set());
@@ -148,7 +165,8 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
148
165
  (inputMessage.trim().length > 0 ||
149
166
  displayImages.length > 0 ||
150
167
  displayVideos.length > 0) &&
151
- (displayImages.length <= 10 || displayVideos.length <= 10);
168
+ (displayImages.length <= MAX_MEDIA_ATTACHMENTS ||
169
+ displayVideos.length <= MAX_MEDIA_ATTACHMENTS);
152
170
 
153
171
  const { renderInput, renderSuggestions } = useMention({
154
172
  value: inputMessage,
@@ -170,7 +188,26 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
170
188
  },
171
189
  });
172
190
 
191
+ // The edited post carries its own hydrated children: every post payload
192
+ // ingests `postChildren` into the `post` cache (PAYLOAD2MODEL maps it to
193
+ // 'post'), and postLinkedObject reads them straight back out. So the media is
194
+ // known on the first frame, with no fetch — no second source to reconcile.
195
+ const editChildren = post?.childrenPosts ?? [];
196
+
197
+ // `childrenPosts` drops cache misses silently (postLinkedObject filters with
198
+ // isNonNullable), so a short array means we cannot see every child. There is
199
+ // no fallback fetch behind it, so treat that state as read-only: no
200
+ // attachment bar, no Save.
201
+ const editChildrenIncomplete =
202
+ isEditMode && editChildren.length !== (post?.children?.length ?? 0);
203
+
173
204
  const checkIsEditValid = () => {
205
+ // `type` in onPressPost is derived from displayImages/displayVideos, so
206
+ // saving while a child is missing submits it as removed — `attachments: []`
207
+ // and type 'text' strips the post's media outright. Nothing the user can do
208
+ // in the composer makes an unrendered child safe to save, so key this on
209
+ // completeness alone rather than on hasChangedAttachment.
210
+ if (editChildrenIncomplete) return false;
174
211
  return (
175
212
  isInputValid &&
176
213
  (inputMessage !== (post?.data as Amity.ContentDataText)?.text ||
@@ -192,9 +229,34 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
192
229
  []
193
230
  );
194
231
 
195
- // Load the edited post's child media the same way the web UIKit does
196
- // (usePostByIds → batch getPostByIds), instead of the racy per-id fetch.
197
- const childrenPosts = usePostByIds(post?.children ?? []);
232
+ // A child post's file is usually already in the SDK cache, where the linked
233
+ // object exposes it synchronously (getImageInfo / getVideoInfo /
234
+ // getVideoThumbnailInfo are `pullFromCache(['file','get', …])` over the very
235
+ // ids we'd otherwise refetch). Use the cached `fileUrl` when it's there and
236
+ // fall back to the round trip on a cache miss. `fileUrlWithSize` is just
237
+ // `?size=`, so both branches produce the same url.
238
+ const resolveFileUrl = useCallback(
239
+ async (fileUrl: string | undefined, fileId: string | undefined) => {
240
+ if (fileUrl)
241
+ return FileRepository.fileUrlWithSize(fileUrl, ImageSizeState.full);
242
+ if (!fileId) return undefined;
243
+
244
+ // Resolve the file directly rather than through useFile: that hook
245
+ // answers a miss with the default *avatar* image, which is right for a
246
+ // profile picture and wrong for post media. A video whose thumbnail the
247
+ // server has not produced yet has no file to resolve, and handing back an
248
+ // avatar made the carousel render a person icon behind the play button as
249
+ // though it were the real frame — and, because the url looked valid, it
250
+ // was also stored as the local thumbnail the feed falls back on.
251
+ const file = await FileRepository.getFile(fileId);
252
+ const resolved = file?.data?.fileUrl;
253
+
254
+ return resolved
255
+ ? FileRepository.fileUrlWithSize(resolved, ImageSizeState.full)
256
+ : undefined;
257
+ },
258
+ []
259
+ );
198
260
 
199
261
  const getPostInfo = useCallback(
200
262
  async (response: Amity.Post[]) => {
@@ -205,38 +267,57 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
205
267
  for (const item of response) {
206
268
  if (item?.dataType === 'image') {
207
269
  const fileId = (item as Amity.Post<'image'>)?.data?.fileId;
208
- const url = await getImage({
209
- fileId: fileId,
210
- imageSize: ImageSizeState.full,
211
- });
270
+ // Dimensions for composer frame-ratio classification (REQ-003d).
271
+ const imageInfo = item?.getImageInfo?.();
272
+ const url = await resolveFileUrl(imageInfo?.fileUrl, fileId);
212
273
  images.push({
213
274
  url,
214
275
  fileId,
215
276
  fileName: fileId,
277
+ // A hydrated child has no local pick behind it, so its identity
278
+ // is the file it already is. Unique per child, stable for the
279
+ // whole session, and never equal to a picker file name — so a
280
+ // later pick is de-duplicated against the other staged picks
281
+ // only, which is the most the picker can tell us.
282
+ localId: fileId ?? item.postId,
216
283
  isUploaded: true,
217
284
  postId: item.postId,
285
+ width: imageInfo?.getWidth?.(),
286
+ height: imageInfo?.getHeight?.(),
218
287
  });
219
288
  } else if (item?.dataType === 'video') {
220
289
  const videoData = (item as Amity.Post<'video'>)?.data;
221
290
  const fileId = videoData?.videoFileId?.original;
222
291
  const thumbnailFileId = videoData?.thumbnailFileId;
223
- const fileUrls = await Promise.allSettled(
224
- [fileId, thumbnailFileId].map(async (id) => {
225
- return await getImage({
226
- fileId: id,
227
- imageSize: ImageSizeState.full,
228
- });
229
- })
230
- );
292
+ // STORED dims + rotation; rotation is applied at classify time
293
+ // (SelectedMediaComponent REQ-003d1 / AmityVideo REQ-SDK-002).
294
+ const videoInfo = item?.getVideoInfo?.();
295
+ const thumbnailInfo = item?.getVideoThumbnailInfo?.();
296
+ // Settle the two urls independently: a missing or unreachable
297
+ // thumbnail must not cost us the video itself. `Promise.all` would
298
+ // reject the pair, abort this loop, and leave displayVideos empty
299
+ // — which now also keeps Save disabled (see checkIsEditValid).
300
+ const [urlResult, thumbNailResult] = await Promise.allSettled([
301
+ resolveFileUrl(videoInfo?.fileUrl, fileId),
302
+ resolveFileUrl(thumbnailInfo?.fileUrl, thumbnailFileId),
303
+ ]);
304
+ const url =
305
+ urlResult.status === 'fulfilled' ? urlResult.value : undefined;
306
+ const thumbNail =
307
+ thumbNailResult.status === 'fulfilled'
308
+ ? thumbNailResult.value
309
+ : undefined;
231
310
  videos.push({
232
- //@ts-ignore
233
- url: fileUrls[0]?.value,
311
+ url,
234
312
  fileId: fileId,
235
313
  fileName: fileId,
314
+ localId: fileId ?? item.postId,
236
315
  isUploaded: true,
237
- //@ts-ignore
238
- thumbNail: fileUrls[1]?.value,
316
+ thumbNail,
239
317
  postId: item.postId,
318
+ width: videoInfo?.getWidth?.(),
319
+ height: videoInfo?.getHeight?.(),
320
+ rotation: videoInfo?.getRotation?.(),
240
321
  });
241
322
  }
242
323
  }
@@ -251,7 +332,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
251
332
  console.log('error: ', error);
252
333
  }
253
334
  },
254
- [getImage]
335
+ [resolveFileUrl]
255
336
  );
256
337
 
257
338
  useEffect(() => {
@@ -259,9 +340,39 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
259
340
  return () => setDeletedPostIds([]);
260
341
  }, []);
261
342
 
343
+ // `post` comes from route params, so `editChildren` is reference-stable and
344
+ // this hydrates once. The hasChangedAttachment guard is belt-and-braces:
345
+ // getPostInfo assigns displayImages/displayVideos wholesale, so a re-run must
346
+ // never be able to wipe media the user added.
262
347
  useEffect(() => {
263
- if (childrenPosts.length > 0) getPostInfo(childrenPosts);
264
- }, [getPostInfo, childrenPosts]);
348
+ if (editChildren.length === 0 || hasChangedAttachment) return;
349
+ getPostInfo(editChildren);
350
+ }, [getPostInfo, post?.childrenPosts, hasChangedAttachment]);
351
+
352
+ // Media type of the post being edited, read off the children's dataType —
353
+ // the same signal the web UIKit feeds into useMediaAttachmentVisible.
354
+ const editMediaType = useMemo(() => {
355
+ if (editChildren.some((item) => item?.dataType === 'image'))
356
+ return mediaAttachment.image;
357
+ if (editChildren.some((item) => item?.dataType === 'video'))
358
+ return mediaAttachment.video;
359
+ return null;
360
+ }, [post?.childrenPosts]);
361
+
362
+ // The post's single media type, as every media entry point must see it: the
363
+ // staged attachments when there are any, otherwise the type the edited post
364
+ // already has. Read the display arrays rather than `chosenMediaType`, which
365
+ // is assigned in an effect and so trails them by a render. Once the user has
366
+ // cleared the attachments itself the post has no type again and all three
367
+ // buttons come back, matching web.
368
+ const activeMediaType =
369
+ displayImages.length > 0
370
+ ? mediaAttachment.image
371
+ : displayVideos.length > 0
372
+ ? mediaAttachment.video
373
+ : hasChangedAttachment
374
+ ? null
375
+ : editMediaType;
265
376
 
266
377
  const getMentionPositions = useCallback(
267
378
  (text: string, mentioneeIds: string[]) => {
@@ -420,6 +531,23 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
420
531
  return;
421
532
  }
422
533
  dispatch(hideToastMessage());
534
+ // The server has no thumbnail for these videos until it finishes
535
+ // transcoding, so hand the feed the frames the composer already decoded
536
+ // to bridge that window (PDT-4904, web parity: LayoutProvider
537
+ // videoThumbnail). Keyed by the uploaded original video's fileId, which
538
+ // is what the created post exposes as `data.videoFileId.original`.
539
+ if (type === 'video' && displayVideos.length > 0) {
540
+ dispatch(
541
+ setLocalVideoThumbnails({
542
+ postId: response.postId,
543
+ // Only frames that actually decoded: an entry with no thumbnail
544
+ // would still satisfy the feed's lookup and suppress its own
545
+ // pending state, leaving the frame blank instead of showing the
546
+ // play affordance over a plain background.
547
+ videos: toLocalVideoThumbnails(displayVideos),
548
+ })
549
+ );
550
+ }
423
551
  if (
424
552
  targetType === 'community' &&
425
553
  (effectiveCommunity?.postSetting === 'ADMIN_REVIEW_POST_REQUIRED' ||
@@ -473,6 +601,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
473
601
  mentionUsers,
474
602
  mentionsPosition,
475
603
  onPressClose,
604
+ setLocalVideoThumbnails,
476
605
  post,
477
606
  showToastMessage,
478
607
  targetId,
@@ -506,6 +635,14 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
506
635
  return {
507
636
  url: url,
508
637
  fileName: fileName,
638
+ // Camera captures land on a freshly generated temp path, so the path
639
+ // itself is the identity: unique per shot, and stable once the upload
640
+ // rewrites `url` to the remote one. Deliberately no
641
+ // `localFileName` — two shots of the same scene are two distinct
642
+ // attachments and must never de-duplicate against each other, nor
643
+ // against a library pick that happens to share a basename.
644
+ localId: url,
645
+ localUrl: url,
509
646
  fileId: '',
510
647
  isUploaded: false,
511
648
  };
@@ -521,12 +658,18 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
521
658
 
522
659
  const pickCamera = useCallback(
523
660
  async (mediaType: 'mixed' | 'photo' | 'video') => {
524
- if (mediaType === 'photo' && displayImages.length === 10)
661
+ if (
662
+ mediaType === 'photo' &&
663
+ displayImages.length >= MAX_MEDIA_ATTACHMENTS
664
+ )
525
665
  return Alert.alert(
526
666
  'Maximum upload limit reached',
527
667
  "You've reached the upload limit of 10 images. Any additional images will not be saved."
528
668
  );
529
- if (mediaType === 'video' && displayVideos.length === 10)
669
+ if (
670
+ mediaType === 'video' &&
671
+ displayVideos.length >= MAX_MEDIA_ATTACHMENTS
672
+ )
530
673
  return Alert.alert(
531
674
  'Maximum upload limit reached',
532
675
  "You've reached the upload limit of 10 videos. Any additional videos will not be saved."
@@ -567,22 +710,34 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
567
710
  [displayImages.length, displayVideos.length, processMedia]
568
711
  );
569
712
  const onPressCamera = useCallback(async () => {
570
- if (displayImages.length > 0) return pickCamera('photo');
571
- if (displayVideos.length > 0) return pickCamera('video');
713
+ // The camera button is always rendered — unlike photo/video, neither
714
+ // attachment bar hides it on `chosenMediaType` — so this is the one entry
715
+ // point that must decide the capture type itself. Keying it on the staged
716
+ // arrays alone offered both types while an edited post's media was still
717
+ // hydrating, which is how a video could be shot onto an image post.
718
+ if (activeMediaType === mediaAttachment.image) return pickCamera('photo');
719
+ if (activeMediaType === mediaAttachment.video) return pickCamera('video');
572
720
  if (Platform.OS === 'ios') return pickCamera('mixed');
573
721
  Alert.alert('Open Camera', null, [
574
722
  { text: 'Photo', onPress: async () => pickCamera('photo') },
575
723
  { text: 'Video', onPress: async () => pickCamera('video') },
576
724
  ]);
577
- }, [displayImages.length, displayVideos.length, pickCamera]);
725
+ }, [activeMediaType, pickCamera]);
578
726
 
579
727
  // Open the native OS camera roll (matches the web UIKit's native file
580
728
  // picker). Appends the picked media, de-duplicating by uri and capping at 10.
581
729
  const pickFromLibrary = useCallback(
582
730
  async (mediaType: 'photo' | 'video') => {
583
731
  const isPhoto = mediaType === 'photo';
732
+ // A post holds one media type. The bar hides the mismatched button, but
733
+ // guard the entry point too so a tap landing before the button updates
734
+ // can't replace the existing attachments with the other type.
735
+ const pickedType = isPhoto
736
+ ? mediaAttachment.image
737
+ : mediaAttachment.video;
738
+ if (activeMediaType && activeMediaType !== pickedType) return;
584
739
  const current = isPhoto ? displayImages.length : displayVideos.length;
585
- if (current >= 10)
740
+ if (current >= MAX_MEDIA_ATTACHMENTS)
586
741
  return Alert.alert(
587
742
  'Maximum upload limit reached',
588
743
  `You've reached the upload limit of 10 ${
@@ -592,36 +747,80 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
592
747
  const result: ImagePicker.ImagePickerResponse = await launchImageLibrary({
593
748
  mediaType,
594
749
  quality: 1,
595
- selectionLimit: 10 - current,
750
+ selectionLimit: MAX_MEDIA_ATTACHMENTS - current,
751
+ // Required for `asset.id`, which is what recognises a re-pick of an
752
+ // asset already staged. Without it the iOS picker builds its
753
+ // PHPickerConfiguration without a photo library, so results carry no
754
+ // assetIdentifier and the native side never resolves a PHAsset —
755
+ // leaving `id` undefined and the fallback key, `fileName`, a fresh
756
+ // UUID per pick. Both dedupe keys were therefore unique every time
757
+ // and picking the same photo twice staged it twice. Android was
758
+ // unaffected: its filename is the MediaStore display name, stable
759
+ // across picks.
760
+ includeExtra: true,
596
761
  });
597
762
  if (result.didCancel || !result.assets?.length) return;
598
763
 
599
- const setter = isPhoto ? setDisplayImages : setDisplayVideos;
600
- setter((prev) => {
601
- // Silently drop duplicates, matching the web UIKit (which dedups by
602
- // file name). The picker copies each pick to a fresh temp uri, so uri
603
- // is not stable across picks — the original `fileName` is.
604
- const seen = new Set(prev.map((m) => m.fileName));
605
- const additions: IDisplayImage[] = [];
606
- for (const asset of result.assets ?? []) {
607
- if (!asset.uri) continue;
608
- const fileName =
609
- asset.fileName ??
610
- asset.uri.substring(asset.uri.lastIndexOf('/') + 1);
611
- if (seen.has(fileName)) continue; // duplicate → silently drop
612
- seen.add(fileName);
613
- additions.push({
764
+ // iOS video is the one pick whose dims cannot be trusted: the picker
765
+ // reports the track's stored `naturalSize` with no rotation alongside
766
+ // it, so a portrait iPhone clip arrives as 1920×1080 and would
767
+ // classify the frame 16:9 (PDT-4904). Images on both platforms, and
768
+ // Android video (its extractor swaps w/h on 90°/270°), are already
769
+ // display-oriented and unaffected. With no rotation to correct the iOS
770
+ // value by, carry nothing and let SelectedMediaComponent measure the
771
+ // generated thumbnail, which is a rendered frame.
772
+ const isIosVideoDimsUnusable = !isPhoto && Platform.OS === 'ios';
773
+ const candidates = (result.assets ?? []).flatMap((asset) => {
774
+ if (!asset.uri) return [];
775
+ const fileName =
776
+ asset.fileName ?? asset.uri.substring(asset.uri.lastIndexOf('/') + 1);
777
+ return [
778
+ {
614
779
  url: asset.uri,
615
780
  fileName,
781
+ // `asset.id` is the library's own identifier for the asset (PHAsset
782
+ // localIdentifier / MediaStore id) and is the one key that survives
783
+ // the picker copying the pick to a new temp uri every time. It is
784
+ // not populated by every picker configuration, so fall back to the
785
+ // file name and keep the name as a second key either way — that is
786
+ // what catches a re-pick when no id came back.
787
+ localId: asset.id ?? fileName,
788
+ localUrl: asset.uri,
789
+ localFileName: fileName,
616
790
  fileId: '',
617
791
  isUploaded: false,
618
- });
619
- }
620
- const updated = [...prev, ...additions];
621
- return updated.length > 10 ? updated.slice(0, 10) : updated;
792
+ width: isIosVideoDimsUnusable ? undefined : asset.width,
793
+ height: isIosVideoDimsUnusable ? undefined : asset.height,
794
+ },
795
+ ];
622
796
  });
797
+
798
+ // Stage the picks and claim their upload slots in one go. Staging alone
799
+ // used to flip `isInputValid` true on the very next render — the frames
800
+ // exist, and `uploadingSources` is still empty because each child only
801
+ // registers from its own mount effect — so Post sat enabled for ~940 ms
802
+ // with nothing uploaded and a quick tap posted media that had no fileId
803
+ // yet. Seeding here makes "staged" and "gated" the same update; children
804
+ // still clear their own entry when they finish, and the unmount cleanup
805
+ // still covers a frame removed mid-flight.
806
+ const staged = isPhoto ? displayImages : displayVideos;
807
+ const accepted = appendWithinCap(
808
+ staged,
809
+ dedupeMediaPicks(staged, candidates)
810
+ );
811
+ // Only the entries that actually survived dedupe and the cap — seeding a
812
+ // key no child will ever mount for would leave Post disabled forever.
813
+ const newlyStaged = accepted.slice(staged.length);
814
+ const setter = isPhoto ? setDisplayImages : setDisplayVideos;
815
+ setter(accepted);
816
+ setUploadingSources((prev) =>
817
+ newlyStaged.reduce(
818
+ (acc, item) => toggleUploadingSource(acc, item.url, true),
819
+ prev
820
+ )
821
+ );
623
822
  },
624
- [displayImages.length, displayVideos.length]
823
+ [activeMediaType, displayImages, displayVideos]
625
824
  );
626
825
 
627
826
  const onPressImage = useCallback(
@@ -634,6 +833,19 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
634
833
  [pickFromLibrary]
635
834
  );
636
835
 
836
+ // Children report their own upload start/end keyed by `source`, and a child
837
+ // that unmounts mid-upload (frame removed while still uploading) clears its
838
+ // own entry from its cleanup — otherwise the set would never empty and Post
839
+ // would stay disabled forever.
840
+ const handleUploadingChange = useCallback(
841
+ (uploading: boolean, source: string) => {
842
+ setUploadingSources((prev) =>
843
+ toggleUploadingSource(prev, source, uploading)
844
+ );
845
+ },
846
+ []
847
+ );
848
+
637
849
  const handleImageUploadError = useCallback(
638
850
  (hasError: boolean, source: string) => {
639
851
  setImageErrors((prev) => {
@@ -667,7 +879,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
667
879
  const handleOnCloseImage = useCallback(
668
880
  (originalPath: string, _, postId: string) => {
669
881
  setHasChangedAttachment(true);
670
- setDeletedPostIds((prev) => [...prev, postId]);
882
+ if (postId) setDeletedPostIds((prev) => [...prev, postId]);
671
883
  setImageErrors((prev) => {
672
884
  const newSet = new Set(prev);
673
885
  newSet.delete(originalPath);
@@ -685,7 +897,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
685
897
  const handleOnCloseVideo = useCallback(
686
898
  (originalPath: string, _, postId: string) => {
687
899
  setHasChangedAttachment(true);
688
- setDeletedPostIds((prev) => [...prev, postId]);
900
+ if (postId) setDeletedPostIds((prev) => [...prev, postId]);
689
901
  setVideoErrors((prev) => {
690
902
  const newSet = new Set(prev);
691
903
  newSet.delete(originalPath);
@@ -701,19 +913,22 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
701
913
  []
702
914
  );
703
915
  const handleOnFinishImage = useCallback(
704
- (fileId: string, fileUrl: string, fileName: string, index: number) => {
916
+ (
917
+ fileId: string,
918
+ fileUrl: string,
919
+ fileName: string,
920
+ _index: number,
921
+ originalPath: string
922
+ ) => {
705
923
  setHasChangedAttachment(true);
706
- const imageObject: IDisplayImage = {
707
- url: fileUrl,
708
- fileId: fileId,
709
- fileName: fileName,
710
- isUploaded: true,
711
- };
712
- setDisplayImages((prevData) => {
713
- const newData = [...prevData];
714
- newData[index] = imageObject;
715
- return newData;
716
- });
924
+ setDisplayImages((prevData) =>
925
+ applyFinishedUpload(prevData, originalPath, {
926
+ url: fileUrl,
927
+ fileId: fileId,
928
+ fileName: fileName,
929
+ isUploaded: true,
930
+ })
931
+ );
717
932
  },
718
933
  []
719
934
  );
@@ -722,23 +937,20 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
722
937
  fileId: string,
723
938
  fileUrl: string,
724
939
  fileName: string,
725
- index: number,
726
- _,
940
+ _index: number,
941
+ originalPath: string,
727
942
  thumbnail: string
728
943
  ) => {
729
944
  setHasChangedAttachment(true);
730
- const imageObject: IDisplayImage = {
731
- url: fileUrl,
732
- fileId: fileId,
733
- fileName: fileName,
734
- isUploaded: true,
735
- thumbNail: thumbnail,
736
- };
737
- setDisplayVideos((prevData) => {
738
- const newData = [...prevData];
739
- newData[index] = imageObject;
740
- return newData;
741
- });
945
+ setDisplayVideos((prevData) =>
946
+ applyFinishedUpload(prevData, originalPath, {
947
+ url: fileUrl,
948
+ fileId: fileId,
949
+ fileName: fileName,
950
+ isUploaded: true,
951
+ thumbNail: thumbnail,
952
+ })
953
+ );
742
954
  },
743
955
  []
744
956
  );
@@ -746,17 +958,18 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
746
958
  // PDT-4310 / PDT-4312: at the 10-attachment cap the camera + gallery icons
747
959
  // are disabled until the user removes a frame.
748
960
  const isMediaCapReached =
749
- displayImages.length >= 10 || displayVideos.length >= 10;
961
+ displayImages.length >= MAX_MEDIA_ATTACHMENTS ||
962
+ displayVideos.length >= MAX_MEDIA_ATTACHMENTS;
750
963
 
751
964
  const renderDetailedAttachment = useCallback(() => {
752
- if (isEditMode) return null;
965
+ if (editChildrenIncomplete) return null;
753
966
  if (shouldShowDetailAttachment) {
754
967
  return (
755
968
  <AmityDetailedMediaAttachmentComponent
756
969
  onPressCamera={onPressCamera}
757
970
  onPressImage={onPressImage}
758
971
  onPressVideo={onPressVideo}
759
- chosenMediaType={chosenMediaType}
972
+ chosenMediaType={activeMediaType}
760
973
  onHeightChange={setAttachmentBarHeight}
761
974
  disabled={isMediaCapReached}
762
975
  />
@@ -767,14 +980,14 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
767
980
  onPressCamera={onPressCamera}
768
981
  onPressImage={onPressImage}
769
982
  onPressVideo={onPressVideo}
770
- chosenMediaType={chosenMediaType}
983
+ chosenMediaType={activeMediaType}
771
984
  onHeightChange={setAttachmentBarHeight}
772
985
  disabled={isMediaCapReached}
773
986
  />
774
987
  );
775
988
  }, [
776
- chosenMediaType,
777
- isEditMode,
989
+ activeMediaType,
990
+ editChildrenIncomplete,
778
991
  isMediaCapReached,
779
992
  onPressCamera,
780
993
  onPressImage,
@@ -841,7 +1054,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
841
1054
  onLoadFinish={handleOnFinishImage}
842
1055
  onUploadError={handleImageUploadError}
843
1056
  isEditMode={isEditMode}
844
- setIsUploading={setIsUploading}
1057
+ onUploadingChange={handleUploadingChange}
845
1058
  />
846
1059
  )}
847
1060
  {displayVideos.length > 0 && (
@@ -852,7 +1065,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
852
1065
  onLoadFinish={handleOnFinishVideo}
853
1066
  onUploadError={handleVideoUploadError}
854
1067
  isEditMode={isEditMode}
855
- setIsUploading={setIsUploading}
1068
+ onUploadingChange={handleUploadingChange}
856
1069
  />
857
1070
  )}
858
1071
  </View>
@@ -12,6 +12,7 @@ export * from './useUiKitConfig';
12
12
  export * from './useAmityGlobalSearchViewModel';
13
13
  export * from './useCommunities';
14
14
  export * from './useCommunity';
15
+ export * from './useCommunityPermission';
15
16
  export * from './useCategory';
16
17
  export * from './useUiKitReference';
17
18
  export * from './isModerator';