@amityco/react-native-social-uikit 4.1.0 → 4.1.1-11cdc854.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 (156) hide show
  1. package/lib/commonjs/core/assets/icons/xml.js +55 -1
  2. package/lib/commonjs/core/assets/icons/xml.js.map +1 -1
  3. package/lib/commonjs/core/hooks/usePostByIds.js +38 -0
  4. package/lib/commonjs/core/hooks/usePostByIds.js.map +1 -0
  5. package/lib/commonjs/social/components/LoadingImage/index.js +4 -3
  6. package/lib/commonjs/social/components/LoadingImage/index.js.map +1 -1
  7. package/lib/commonjs/social/components/LoadingImage/styles.js +4 -0
  8. package/lib/commonjs/social/components/LoadingImage/styles.js.map +1 -1
  9. package/lib/commonjs/social/components/LoadingVideo/index.js +12 -6
  10. package/lib/commonjs/social/components/LoadingVideo/index.js.map +1 -1
  11. package/lib/commonjs/social/components/LoadingVideo/styles.js +4 -0
  12. package/lib/commonjs/social/components/LoadingVideo/styles.js.map +1 -1
  13. package/lib/commonjs/social/components/MediaViewer/MediaViewer.js +266 -0
  14. package/lib/commonjs/social/components/MediaViewer/MediaViewer.js.map +1 -0
  15. package/lib/commonjs/social/components/MediaViewer/index.js +13 -0
  16. package/lib/commonjs/social/components/MediaViewer/index.js.map +1 -0
  17. package/lib/commonjs/social/components/MediaViewer/styles.js +116 -0
  18. package/lib/commonjs/social/components/MediaViewer/styles.js.map +1 -0
  19. package/lib/commonjs/social/components/PostContent/index.js +75 -120
  20. package/lib/commonjs/social/components/PostContent/index.js.map +1 -1
  21. package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js +180 -0
  22. package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -0
  23. package/lib/commonjs/social/components/SelectedMediaComponent/index.js +13 -0
  24. package/lib/commonjs/social/components/SelectedMediaComponent/index.js.map +1 -0
  25. package/lib/commonjs/social/components/SelectedMediaComponent/styles.js +43 -0
  26. package/lib/commonjs/social/components/SelectedMediaComponent/styles.js.map +1 -0
  27. package/lib/commonjs/social/constants/index.js +11 -0
  28. package/lib/commonjs/social/constants/index.js.map +1 -0
  29. package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js +280 -0
  30. package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js.map +1 -0
  31. package/lib/commonjs/social/elements/PostMediaElement/index.js +13 -0
  32. package/lib/commonjs/social/elements/PostMediaElement/index.js.map +1 -0
  33. package/lib/commonjs/social/elements/PostMediaElement/styles.js +117 -0
  34. package/lib/commonjs/social/elements/PostMediaElement/styles.js.map +1 -0
  35. package/lib/commonjs/social/features/post/Composer/PostComposer.js +91 -106
  36. package/lib/commonjs/social/features/post/Composer/PostComposer.js.map +1 -1
  37. package/lib/commonjs/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js +17 -7
  38. package/lib/commonjs/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js.map +1 -1
  39. package/lib/commonjs/social/features/post/components/MediaAttachment/MediaAttachment.js +17 -7
  40. package/lib/commonjs/social/features/post/components/MediaAttachment/MediaAttachment.js.map +1 -1
  41. package/lib/commonjs/social/utils/getFrameRatio.js +52 -0
  42. package/lib/commonjs/social/utils/getFrameRatio.js.map +1 -0
  43. package/lib/commonjs/social/utils/getIndicatorConfig.js +59 -0
  44. package/lib/commonjs/social/utils/getIndicatorConfig.js.map +1 -0
  45. package/lib/module/core/assets/icons/xml.js +48 -0
  46. package/lib/module/core/assets/icons/xml.js.map +1 -1
  47. package/lib/module/core/hooks/usePostByIds.js +32 -0
  48. package/lib/module/core/hooks/usePostByIds.js.map +1 -0
  49. package/lib/module/social/components/LoadingImage/index.js +4 -3
  50. package/lib/module/social/components/LoadingImage/index.js.map +1 -1
  51. package/lib/module/social/components/LoadingImage/styles.js +4 -0
  52. package/lib/module/social/components/LoadingImage/styles.js.map +1 -1
  53. package/lib/module/social/components/LoadingVideo/index.js +13 -7
  54. package/lib/module/social/components/LoadingVideo/index.js.map +1 -1
  55. package/lib/module/social/components/LoadingVideo/styles.js +4 -0
  56. package/lib/module/social/components/LoadingVideo/styles.js.map +1 -1
  57. package/lib/module/social/components/MediaViewer/MediaViewer.js +259 -0
  58. package/lib/module/social/components/MediaViewer/MediaViewer.js.map +1 -0
  59. package/lib/module/social/components/MediaViewer/index.js +2 -0
  60. package/lib/module/social/components/MediaViewer/index.js.map +1 -0
  61. package/lib/module/social/components/MediaViewer/styles.js +109 -0
  62. package/lib/module/social/components/MediaViewer/styles.js.map +1 -0
  63. package/lib/module/social/components/PostContent/index.js +77 -122
  64. package/lib/module/social/components/PostContent/index.js.map +1 -1
  65. package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js +174 -0
  66. package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -0
  67. package/lib/module/social/components/SelectedMediaComponent/index.js +2 -0
  68. package/lib/module/social/components/SelectedMediaComponent/index.js.map +1 -0
  69. package/lib/module/social/components/SelectedMediaComponent/styles.js +36 -0
  70. package/lib/module/social/components/SelectedMediaComponent/styles.js.map +1 -0
  71. package/lib/module/social/constants/index.js +5 -0
  72. package/lib/module/social/constants/index.js.map +1 -0
  73. package/lib/module/social/elements/PostMediaElement/PostMediaElement.js +273 -0
  74. package/lib/module/social/elements/PostMediaElement/PostMediaElement.js.map +1 -0
  75. package/lib/module/social/elements/PostMediaElement/index.js +2 -0
  76. package/lib/module/social/elements/PostMediaElement/index.js.map +1 -0
  77. package/lib/module/social/elements/PostMediaElement/styles.js +110 -0
  78. package/lib/module/social/elements/PostMediaElement/styles.js.map +1 -0
  79. package/lib/module/social/features/post/Composer/PostComposer.js +94 -109
  80. package/lib/module/social/features/post/Composer/PostComposer.js.map +1 -1
  81. package/lib/module/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js +17 -7
  82. package/lib/module/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js.map +1 -1
  83. package/lib/module/social/features/post/components/MediaAttachment/MediaAttachment.js +17 -7
  84. package/lib/module/social/features/post/components/MediaAttachment/MediaAttachment.js.map +1 -1
  85. package/lib/module/social/utils/getFrameRatio.js +44 -0
  86. package/lib/module/social/utils/getFrameRatio.js.map +1 -0
  87. package/lib/module/social/utils/getIndicatorConfig.js +53 -0
  88. package/lib/module/social/utils/getIndicatorConfig.js.map +1 -0
  89. package/lib/typescript/core/assets/icons/xml.d.ts +7 -0
  90. package/lib/typescript/core/assets/icons/xml.d.ts.map +1 -1
  91. package/lib/typescript/core/hooks/usePostByIds.d.ts +8 -0
  92. package/lib/typescript/core/hooks/usePostByIds.d.ts.map +1 -0
  93. package/lib/typescript/core/types/mention.d.ts +6 -0
  94. package/lib/typescript/core/types/mention.d.ts.map +1 -1
  95. package/lib/typescript/social/components/LoadingImage/index.d.ts +2 -1
  96. package/lib/typescript/social/components/LoadingImage/index.d.ts.map +1 -1
  97. package/lib/typescript/social/components/LoadingImage/styles.d.ts +4 -0
  98. package/lib/typescript/social/components/LoadingImage/styles.d.ts.map +1 -1
  99. package/lib/typescript/social/components/LoadingVideo/index.d.ts +2 -1
  100. package/lib/typescript/social/components/LoadingVideo/index.d.ts.map +1 -1
  101. package/lib/typescript/social/components/LoadingVideo/styles.d.ts +4 -0
  102. package/lib/typescript/social/components/LoadingVideo/styles.d.ts.map +1 -1
  103. package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts +17 -0
  104. package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts.map +1 -0
  105. package/lib/typescript/social/components/MediaViewer/index.d.ts +3 -0
  106. package/lib/typescript/social/components/MediaViewer/index.d.ts.map +1 -0
  107. package/lib/typescript/social/components/MediaViewer/styles.d.ts +99 -0
  108. package/lib/typescript/social/components/MediaViewer/styles.d.ts.map +1 -0
  109. package/lib/typescript/social/components/PostContent/index.d.ts.map +1 -1
  110. package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts +12 -0
  111. package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts.map +1 -0
  112. package/lib/typescript/social/components/SelectedMediaComponent/index.d.ts +3 -0
  113. package/lib/typescript/social/components/SelectedMediaComponent/index.d.ts.map +1 -0
  114. package/lib/typescript/social/components/SelectedMediaComponent/styles.d.ts +25 -0
  115. package/lib/typescript/social/components/SelectedMediaComponent/styles.d.ts.map +1 -0
  116. package/lib/typescript/social/constants/index.d.ts +4 -0
  117. package/lib/typescript/social/constants/index.d.ts.map +1 -0
  118. package/lib/typescript/social/elements/PostMediaElement/PostMediaElement.d.ts +15 -0
  119. package/lib/typescript/social/elements/PostMediaElement/PostMediaElement.d.ts.map +1 -0
  120. package/lib/typescript/social/elements/PostMediaElement/index.d.ts +3 -0
  121. package/lib/typescript/social/elements/PostMediaElement/index.d.ts.map +1 -0
  122. package/lib/typescript/social/elements/PostMediaElement/styles.d.ts +88 -0
  123. package/lib/typescript/social/elements/PostMediaElement/styles.d.ts.map +1 -0
  124. package/lib/typescript/social/features/post/Composer/PostComposer.d.ts.map +1 -1
  125. package/lib/typescript/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.d.ts +3 -0
  126. package/lib/typescript/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.d.ts.map +1 -1
  127. package/lib/typescript/social/features/post/components/MediaAttachment/MediaAttachment.d.ts +3 -0
  128. package/lib/typescript/social/features/post/components/MediaAttachment/MediaAttachment.d.ts.map +1 -1
  129. package/lib/typescript/social/utils/getFrameRatio.d.ts +21 -0
  130. package/lib/typescript/social/utils/getFrameRatio.d.ts.map +1 -0
  131. package/lib/typescript/social/utils/getIndicatorConfig.d.ts +11 -0
  132. package/lib/typescript/social/utils/getIndicatorConfig.d.ts.map +1 -0
  133. package/package.json +3 -3
  134. package/src/core/assets/icons/xml.ts +60 -0
  135. package/src/core/hooks/usePostByIds.ts +33 -0
  136. package/src/core/types/mention.ts +6 -0
  137. package/src/social/components/LoadingImage/index.tsx +12 -2
  138. package/src/social/components/LoadingImage/styles.ts +4 -0
  139. package/src/social/components/LoadingVideo/index.tsx +28 -4
  140. package/src/social/components/LoadingVideo/styles.ts +4 -0
  141. package/src/social/components/MediaViewer/MediaViewer.tsx +384 -0
  142. package/src/social/components/MediaViewer/index.tsx +2 -0
  143. package/src/social/components/MediaViewer/styles.ts +109 -0
  144. package/src/social/components/PostContent/index.tsx +86 -159
  145. package/src/social/components/SelectedMediaComponent/SelectedMediaComponent.tsx +236 -0
  146. package/src/social/components/SelectedMediaComponent/index.ts +2 -0
  147. package/src/social/components/SelectedMediaComponent/styles.ts +38 -0
  148. package/src/social/constants/index.ts +4 -0
  149. package/src/social/elements/PostMediaElement/PostMediaElement.tsx +362 -0
  150. package/src/social/elements/PostMediaElement/index.tsx +5 -0
  151. package/src/social/elements/PostMediaElement/styles.ts +99 -0
  152. package/src/social/features/post/Composer/PostComposer.tsx +107 -123
  153. package/src/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx +10 -3
  154. package/src/social/features/post/components/MediaAttachment/MediaAttachment.tsx +14 -3
  155. package/src/social/utils/getFrameRatio.ts +42 -0
  156. package/src/social/utils/getIndicatorConfig.ts +62 -0
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  Alert,
3
- FlatList,
4
3
  Keyboard,
5
4
  KeyboardAvoidingView,
6
5
  Platform,
@@ -38,22 +37,18 @@ import uiSlice from '../../../../core/stores/slices/uiSlice';
38
37
  import { amityPostsFormatter } from '../../../../core/utils/post';
39
38
  import useAuth from '../../../../core/hooks/useAuth';
40
39
  import globalfeedSlice from '../../../../core/stores/slices/globalfeedSlice';
41
- import {
42
- createPostToFeed,
43
- editPost,
44
- getPostById,
45
- } from '../../../../core/legacy/feed';
40
+ import { createPostToFeed, editPost } from '../../../../core/legacy/feed';
41
+ import { usePostByIds } from '../../../../core/hooks/usePostByIds';
46
42
  import TextKeyElement from '../../../elements/TextKeyElement/TextKeyElement';
47
43
  import AmityMediaAttachmentComponent from '../components/MediaAttachment';
48
44
  import AmityDetailedMediaAttachmentComponent from '../components/DetailedMediaAttachment';
49
45
  import { useKeyboardStatus } from '../../../hooks';
50
46
  import ImagePicker, {
51
- launchImageLibrary,
52
47
  type Asset,
53
48
  launchCamera,
49
+ launchImageLibrary,
54
50
  } from 'react-native-image-picker';
55
- import LoadingImage from '../../../components/LoadingImage';
56
- import LoadingVideo from '../../../components/LoadingVideo';
51
+ import { SelectedMediaComponent } from '../../../components/SelectedMediaComponent';
57
52
  import { NativeStackNavigationProp } from '@react-navigation/native-stack';
58
53
  import { RootStackParamList } from '../../../../core/routes/RouteParamList';
59
54
  import {
@@ -197,36 +192,38 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
197
192
  []
198
193
  );
199
194
 
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 ?? []);
198
+
200
199
  const getPostInfo = useCallback(
201
- async (postArray: string[]) => {
200
+ async (response: Amity.Post[]) => {
202
201
  try {
203
- const response = await Promise.all(
204
- postArray.map(async (id: string) => {
205
- const { data } = await getPostById(id);
206
- return data;
207
- })
208
- );
209
-
210
202
  const images: IDisplayImage[] = [];
211
203
  const videos: IDisplayImage[] = [];
212
204
 
213
205
  for (const item of response) {
214
206
  if (item?.dataType === 'image') {
215
- const fileId = item?.data?.fileId;
207
+ const fileId = (item as Amity.Post<'image'>)?.data?.fileId;
216
208
  const url = await getImage({
217
209
  fileId: fileId,
218
210
  imageSize: ImageSizeState.full,
219
211
  });
212
+ // Dimensions for composer frame-ratio classification (REQ-003d).
213
+ const imageInfo = (item as any)?.getImageInfo?.();
220
214
  images.push({
221
215
  url,
222
216
  fileId,
223
217
  fileName: fileId,
224
218
  isUploaded: true,
225
219
  postId: item.postId,
220
+ width: imageInfo?.getWidth?.(),
221
+ height: imageInfo?.getHeight?.(),
226
222
  });
227
223
  } else if (item?.dataType === 'video') {
228
- const fileId = item?.data?.videoFileId?.original;
229
- const thumbnailFileId = item?.data?.thumbnailFileId;
224
+ const videoData = (item as Amity.Post<'video'>)?.data;
225
+ const fileId = videoData?.videoFileId?.original;
226
+ const thumbnailFileId = videoData?.thumbnailFileId;
230
227
  const fileUrls = await Promise.allSettled(
231
228
  [fileId, thumbnailFileId].map(async (id) => {
232
229
  return await getImage({
@@ -235,6 +232,9 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
235
232
  });
236
233
  })
237
234
  );
235
+ // STORED dims + rotation; rotation is applied at classify time
236
+ // (SelectedMediaComponent REQ-003d1 / AmityVideo REQ-SDK-002).
237
+ const videoInfo = (item as any)?.getVideoInfo?.();
238
238
  videos.push({
239
239
  //@ts-ignore
240
240
  url: fileUrls[0]?.value,
@@ -244,6 +244,9 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
244
244
  //@ts-ignore
245
245
  thumbNail: fileUrls[1]?.value,
246
246
  postId: item.postId,
247
+ width: videoInfo?.getWidth?.(),
248
+ height: videoInfo?.getHeight?.(),
249
+ rotation: videoInfo?.getRotation?.(),
247
250
  });
248
251
  }
249
252
  }
@@ -267,8 +270,8 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
267
270
  }, []);
268
271
 
269
272
  useEffect(() => {
270
- post?.children && getPostInfo(post?.children);
271
- }, [getPostInfo, post?.children]);
273
+ if (childrenPosts.length > 0) getPostInfo(childrenPosts);
274
+ }, [getPostInfo, childrenPosts]);
272
275
 
273
276
  const getMentionPositions = useCallback(
274
277
  (text: string, mentioneeIds: string[]) => {
@@ -583,65 +586,67 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
583
586
  ]);
584
587
  }, [displayImages.length, displayVideos.length, pickCamera]);
585
588
 
586
- const onPressImage = useCallback(async () => {
587
- if (displayImages.length === 10)
588
- return Alert.alert(
589
- 'Maximum upload limit reached',
590
- "You've reached the upload limit of 10 images. Any additional images will not be saved."
591
- );
592
- const result: ImagePicker.ImagePickerResponse = await launchImageLibrary({
593
- mediaType: 'photo',
594
- quality: 1,
595
- selectionLimit: 10 - displayImages.length,
596
- });
597
- if (!result.didCancel && result.assets && result.assets.length > 0) {
598
- const imageUriArr: string[] = result.assets.map(
599
- (item: Asset) => item.uri
600
- ) as string[];
601
- const mediaOj = processMedia(imageUriArr);
602
- setDisplayImages((prev) => {
603
- const updatedArray = [...prev, ...mediaOj];
604
- if (updatedArray.length > 10) {
605
- Alert.alert(
606
- 'Maximum number of images exceeded',
607
- 'Maximum number of images that can be uploaded is 10. The rest images will be discarded'
608
- );
609
- return updatedArray.slice(0, 10);
610
- }
611
- return updatedArray;
589
+ // Open the native OS camera roll (matches the web UIKit's native file
590
+ // picker). Appends the picked media, de-duplicating by uri and capping at 10.
591
+ const pickFromLibrary = useCallback(
592
+ async (mediaType: 'photo' | 'video') => {
593
+ const isPhoto = mediaType === 'photo';
594
+ const current = isPhoto ? displayImages.length : displayVideos.length;
595
+ if (current >= 10)
596
+ return Alert.alert(
597
+ 'Maximum upload limit reached',
598
+ `You've reached the upload limit of 10 ${
599
+ isPhoto ? 'images' : 'videos'
600
+ }. Any additional ${isPhoto ? 'images' : 'videos'} will not be saved.`
601
+ );
602
+ const result: ImagePicker.ImagePickerResponse = await launchImageLibrary({
603
+ mediaType,
604
+ quality: 1,
605
+ selectionLimit: 10 - current,
612
606
  });
613
- }
614
- }, [displayImages.length, processMedia]);
615
-
616
- const onPressVideo = useCallback(async () => {
617
- if (displayVideos.length === 10)
618
- return Alert.alert(
619
- 'Maximum upload limit reached',
620
- "You've reached the upload limit of 10 videos. Any additional videos will not be saved."
621
- );
622
- const result: ImagePicker.ImagePickerResponse = await launchImageLibrary({
623
- mediaType: 'video',
624
- quality: 1,
625
- selectionLimit: 10 - displayVideos.length,
626
- });
627
- if (!result.didCancel && result.assets && result.assets.length > 0) {
628
- const videoUriArr: string[] = result.assets.map(
629
- (item: Asset) => item.uri
630
- ) as string[];
631
- const mediaOj = processMedia(videoUriArr);
632
- setDisplayVideos((prev) => {
633
- const updatedArray = [...prev, ...mediaOj];
634
- if (updatedArray.length > 10) {
635
- Alert.alert(
636
- 'Maximum number of videos exceeded',
637
- 'Maximum number of videos that can be uploaded is 10. The rest videos will be discarded'
638
- );
639
- return updatedArray.slice(0, 10);
607
+ if (result.didCancel || !result.assets?.length) return;
608
+
609
+ const setter = isPhoto ? setDisplayImages : setDisplayVideos;
610
+ setter((prev) => {
611
+ // Silently drop duplicates, matching the web UIKit (which dedups by
612
+ // file name). The picker copies each pick to a fresh temp uri, so uri
613
+ // is not stable across picks — the original `fileName` is.
614
+ const seen = new Set(prev.map((m) => m.fileName));
615
+ const additions: IDisplayImage[] = [];
616
+ for (const asset of result.assets ?? []) {
617
+ if (!asset.uri) continue;
618
+ const fileName =
619
+ asset.fileName ??
620
+ asset.uri.substring(asset.uri.lastIndexOf('/') + 1);
621
+ if (seen.has(fileName)) continue; // duplicate → silently drop
622
+ seen.add(fileName);
623
+ additions.push({
624
+ url: asset.uri,
625
+ fileName,
626
+ fileId: '',
627
+ isUploaded: false,
628
+ // Locally-picked dimensions are already display-oriented (the
629
+ // picker's extractor applied any rotation) — no rotation to carry.
630
+ width: asset.width,
631
+ height: asset.height,
632
+ });
640
633
  }
641
- return updatedArray;
634
+ const updated = [...prev, ...additions];
635
+ return updated.length > 10 ? updated.slice(0, 10) : updated;
642
636
  });
643
- }
644
- }, [displayVideos.length, processMedia]);
637
+ },
638
+ [displayImages.length, displayVideos.length]
639
+ );
640
+
641
+ const onPressImage = useCallback(
642
+ () => pickFromLibrary('photo'),
643
+ [pickFromLibrary]
644
+ );
645
+
646
+ const onPressVideo = useCallback(
647
+ () => pickFromLibrary('video'),
648
+ [pickFromLibrary]
649
+ );
645
650
 
646
651
  const handleImageUploadError = useCallback(
647
652
  (hasError: boolean, source: string) => {
@@ -752,6 +757,11 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
752
757
  []
753
758
  );
754
759
 
760
+ // PDT-4310 / PDT-4312: at the 10-attachment cap the camera + gallery icons
761
+ // are disabled until the user removes a frame.
762
+ const isMediaCapReached =
763
+ displayImages.length >= 10 || displayVideos.length >= 10;
764
+
755
765
  const renderDetailedAttachment = useCallback(() => {
756
766
  if (isEditMode) return null;
757
767
  if (shouldShowDetailAttachment) {
@@ -762,6 +772,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
762
772
  onPressVideo={onPressVideo}
763
773
  chosenMediaType={chosenMediaType}
764
774
  onHeightChange={setAttachmentBarHeight}
775
+ disabled={isMediaCapReached}
765
776
  />
766
777
  );
767
778
  }
@@ -772,11 +783,13 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
772
783
  onPressVideo={onPressVideo}
773
784
  chosenMediaType={chosenMediaType}
774
785
  onHeightChange={setAttachmentBarHeight}
786
+ disabled={isMediaCapReached}
775
787
  />
776
788
  );
777
789
  }, [
778
790
  chosenMediaType,
779
791
  isEditMode,
792
+ isMediaCapReached,
780
793
  onPressCamera,
781
794
  onPressImage,
782
795
  onPressVideo,
@@ -835,54 +848,25 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
835
848
  })}
836
849
  <View style={styles.imageContainer}>
837
850
  {displayImages.length > 0 && (
838
- <FlatList
839
- nestedScrollEnabled={true}
840
- scrollEnabled={false}
841
- data={displayImages}
842
- renderItem={({ item, index }) => {
843
- if (!item) return null;
844
- return (
845
- <LoadingImage
846
- source={item.url}
847
- onClose={handleOnCloseImage}
848
- index={index} //TODO: Fix this without index
849
- onLoadFinish={handleOnFinishImage}
850
- onUploadError={handleImageUploadError}
851
- isUploaded={item.isUploaded}
852
- fileId={item.fileId}
853
- fileCount={displayImages.length}
854
- isEditMode={isEditMode}
855
- postId={item.postId}
856
- setIsUploading={setIsUploading}
857
- />
858
- );
859
- }}
860
- numColumns={3}
851
+ <SelectedMediaComponent
852
+ mediaType="image"
853
+ media={displayImages}
854
+ onClose={handleOnCloseImage}
855
+ onLoadFinish={handleOnFinishImage}
856
+ onUploadError={handleImageUploadError}
857
+ isEditMode={isEditMode}
858
+ setIsUploading={setIsUploading}
861
859
  />
862
860
  )}
863
861
  {displayVideos.length > 0 && (
864
- <FlatList
865
- data={displayVideos}
866
- renderItem={({ item, index }) => {
867
- if (!item) return null;
868
- return (
869
- <LoadingVideo
870
- source={item.url}
871
- onClose={handleOnCloseVideo}
872
- index={index} //TODO: Fix this without index
873
- onLoadFinish={handleOnFinishVideo}
874
- onUploadError={handleVideoUploadError}
875
- isUploaded={item.isUploaded}
876
- fileId={item.fileId}
877
- thumbNail={item.thumbNail as string}
878
- fileCount={displayVideos.length}
879
- isEditMode={isEditMode}
880
- postId={item.postId}
881
- setIsUploading={setIsUploading}
882
- />
883
- );
884
- }}
885
- numColumns={3}
862
+ <SelectedMediaComponent
863
+ mediaType="video"
864
+ media={displayVideos}
865
+ onClose={handleOnCloseVideo}
866
+ onLoadFinish={handleOnFinishVideo}
867
+ onUploadError={handleVideoUploadError}
868
+ isEditMode={isEditMode}
869
+ setIsUploading={setIsUploading}
886
870
  />
887
871
  )}
888
872
  </View>
@@ -18,6 +18,9 @@ type AmityDetailedMediaAttachmentComponentType = {
18
18
  onPressVideo: () => void;
19
19
  chosenMediaType?: mediaAttachment;
20
20
  onHeightChange?: (height: number) => void;
21
+ /** When the 10-attachment cap is reached, the camera + gallery icons are
22
+ * disabled and untappable (PDT-4310 / PDT-4312). */
23
+ disabled?: boolean;
21
24
  };
22
25
 
23
26
  const AmityDetailedMediaAttachmentComponent: FC<
@@ -28,6 +31,7 @@ const AmityDetailedMediaAttachmentComponent: FC<
28
31
  onPressVideo,
29
32
  chosenMediaType,
30
33
  onHeightChange,
34
+ disabled = false,
31
35
  }) => {
32
36
  const pageId = PageID.post_composer_page;
33
37
  const componentId = ComponentID.detailed_media_attachment;
@@ -91,8 +95,9 @@ const AmityDetailedMediaAttachmentComponent: FC<
91
95
  <Pressable
92
96
  testID={cameraElement.accessibilityId}
93
97
  accessibilityLabel={cameraElement.accessibilityId}
94
- style={styles.mediaAttachmentBtn}
98
+ style={[styles.mediaAttachmentBtn, disabled && { opacity: 0.4 }]}
95
99
  onPress={onPressCamera}
100
+ disabled={disabled}
96
101
  >
97
102
  <View style={styles.iconContainer}>
98
103
  <SvgXml
@@ -115,8 +120,9 @@ const AmityDetailedMediaAttachmentComponent: FC<
115
120
  <Pressable
116
121
  testID={imageElement.accessibilityId}
117
122
  accessibilityLabel={imageElement.accessibilityId}
118
- style={styles.mediaAttachmentBtn}
123
+ style={[styles.mediaAttachmentBtn, disabled && { opacity: 0.4 }]}
119
124
  onPress={onPressImage}
125
+ disabled={disabled}
120
126
  >
121
127
  <View style={styles.iconContainer}>
122
128
  <SvgXml
@@ -139,8 +145,9 @@ const AmityDetailedMediaAttachmentComponent: FC<
139
145
  <Pressable
140
146
  testID={videoElement.accessibilityId}
141
147
  accessibilityLabel={videoElement.accessibilityId}
142
- style={styles.mediaAttachmentBtn}
148
+ style={[styles.mediaAttachmentBtn, disabled && { opacity: 0.4 }]}
143
149
  onPress={onPressVideo}
150
+ disabled={disabled}
144
151
  >
145
152
  <View style={styles.iconContainer}>
146
153
  <SvgXml
@@ -17,6 +17,9 @@ type AmityMediaAttachmentComponentType = {
17
17
  onPressVideo: () => void;
18
18
  chosenMediaType?: mediaAttachment;
19
19
  onHeightChange?: (height: number) => void;
20
+ /** When the 10-attachment cap is reached, the camera + gallery icons are
21
+ * disabled and untappable (PDT-4310 / PDT-4312). */
22
+ disabled?: boolean;
20
23
  };
21
24
 
22
25
  const AmityMediaAttachmentComponent: FC<AmityMediaAttachmentComponentType> = ({
@@ -25,6 +28,7 @@ const AmityMediaAttachmentComponent: FC<AmityMediaAttachmentComponentType> = ({
25
28
  onPressVideo,
26
29
  chosenMediaType,
27
30
  onHeightChange,
31
+ disabled = false,
28
32
  }) => {
29
33
  const pageId = PageID.post_composer_page;
30
34
  const componentId = ComponentID.media_attachment;
@@ -89,8 +93,9 @@ const AmityMediaAttachmentComponent: FC<AmityMediaAttachmentComponentType> = ({
89
93
  testID={cameraElement.accessibilityId}
90
94
  accessibilityLabel={cameraElement.accessibilityId}
91
95
  onPress={onPressCamera}
96
+ disabled={disabled}
92
97
  >
93
- <View style={styles.iconContainer}>
98
+ <View style={[styles.iconContainer, disabled && { opacity: 0.4 }]}>
94
99
  <SvgXml
95
100
  xml={camera()}
96
101
  width={24}
@@ -107,8 +112,11 @@ const AmityMediaAttachmentComponent: FC<AmityMediaAttachmentComponentType> = ({
107
112
  testID={imageElement.accessibilityId}
108
113
  accessibilityLabel={imageElement.accessibilityId}
109
114
  onPress={onPressImage}
115
+ disabled={disabled}
110
116
  >
111
- <View style={styles.iconContainer}>
117
+ <View
118
+ style={[styles.iconContainer, disabled && { opacity: 0.4 }]}
119
+ >
112
120
  <SvgXml
113
121
  xml={photo()}
114
122
  width={24}
@@ -124,8 +132,11 @@ const AmityMediaAttachmentComponent: FC<AmityMediaAttachmentComponentType> = ({
124
132
  testID={videoElement.accessibilityId}
125
133
  accessibilityLabel={videoElement.accessibilityId}
126
134
  onPress={onPressVideo}
135
+ disabled={disabled}
127
136
  >
128
- <View style={styles.iconContainer}>
137
+ <View
138
+ style={[styles.iconContainer, disabled && { opacity: 0.4 }]}
139
+ >
129
140
  <SvgXml
130
141
  xml={video()}
131
142
  width={24}
@@ -0,0 +1,42 @@
1
+ export type FrameRatio = '16:9' | '1:1' | '4:5';
2
+
3
+ /**
4
+ * Numeric aspect ratio (width / height) for each frame ratio.
5
+ * React Native's `aspectRatio` style takes a number, unlike web's CSS string.
6
+ */
7
+ export const FRAME_RATIO_VALUE: Record<FrameRatio, number> = {
8
+ '16:9': 16 / 9,
9
+ '1:1': 1,
10
+ '4:5': 4 / 5,
11
+ };
12
+
13
+ const LANDSCAPE_THRESHOLD = 1.25;
14
+ const PORTRAIT_THRESHOLD = 0.8;
15
+
16
+ export const DEFAULT_FRAME_RATIO: FrameRatio = '1:1';
17
+
18
+ export function getFrameRatio(width?: number, height?: number): FrameRatio {
19
+ if (!width || !height || height <= 0) return DEFAULT_FRAME_RATIO;
20
+
21
+ const ratio = width / height;
22
+
23
+ if (ratio >= LANDSCAPE_THRESHOLD) return '16:9';
24
+ if (ratio <= PORTRAIT_THRESHOLD) return '4:5';
25
+ return '1:1';
26
+ }
27
+
28
+ /**
29
+ * Swap width/height when a video is rotated 90/270 degrees so the frame ratio
30
+ * reflects how the video is actually displayed. Mirrors web getVideoDisplayDims.
31
+ */
32
+ export function getVideoDisplayDims(video?: {
33
+ width?: number;
34
+ height?: number;
35
+ rotation?: number;
36
+ }): { width?: number; height?: number } {
37
+ const { width, height, rotation } = video ?? {};
38
+ if (width == null || height == null) return { width, height };
39
+ if (Math.abs(rotation ?? 0) % 180 === 90)
40
+ return { width: height, height: width };
41
+ return { width, height };
42
+ }
@@ -0,0 +1,62 @@
1
+ import { MAX_INDICATOR_DOTS } from '../constants';
2
+
3
+ export type IndicatorDotState = 'active' | 'inactive' | 'edge';
4
+
5
+ export type IndicatorDot = {
6
+ key: string;
7
+ state: IndicatorDotState;
8
+ side?: 'left' | 'right';
9
+ };
10
+
11
+ export type IndicatorConfig = {
12
+ dots: IndicatorDot[];
13
+ };
14
+
15
+ export function getIndicatorConfig(
16
+ current: number,
17
+ total: number
18
+ ): IndicatorConfig {
19
+ if (total <= MAX_INDICATOR_DOTS) {
20
+ const dots = Array.from({ length: total }, (_, page) => ({
21
+ key: `page-${page}`,
22
+ state: page === current ? ('active' as const) : ('inactive' as const),
23
+ }));
24
+ return { dots };
25
+ }
26
+
27
+ let windowStart: number;
28
+ let windowEnd: number;
29
+
30
+ if (current <= 1) {
31
+ windowStart = 0;
32
+ windowEnd = 4;
33
+ } else if (current >= total - 3) {
34
+ windowStart = total - 5;
35
+ windowEnd = total - 1;
36
+ } else {
37
+ windowStart = current - 1;
38
+ windowEnd = current + 2;
39
+ }
40
+
41
+ const showLeft = windowStart > 0;
42
+ const showRight = windowEnd < total - 1;
43
+
44
+ const dots: IndicatorDot[] = [];
45
+
46
+ if (showLeft) {
47
+ dots.push({ key: 'edge-left', state: 'edge', side: 'left' });
48
+ }
49
+
50
+ for (let page = windowStart; page <= windowEnd; page += 1) {
51
+ dots.push({
52
+ key: `page-${page}`,
53
+ state: page === current ? 'active' : 'inactive',
54
+ });
55
+ }
56
+
57
+ if (showRight) {
58
+ dots.push({ key: 'edge-right', state: 'edge', side: 'right' });
59
+ }
60
+
61
+ return { dots };
62
+ }