@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.
- package/README.md +7 -0
- package/lib/commonjs/core/assets/icons/post.js +1 -1
- package/lib/commonjs/core/assets/icons/post.js.map +1 -1
- package/lib/commonjs/core/assets/icons/story.js +1 -1
- package/lib/commonjs/core/assets/icons/story.js.map +1 -1
- package/lib/commonjs/core/assets/icons/xml.js +16 -2
- package/lib/commonjs/core/assets/icons/xml.js.map +1 -1
- package/lib/commonjs/core/legacy/file.js +145 -62
- package/lib/commonjs/core/legacy/file.js.map +1 -1
- package/lib/commonjs/core/stores/slices/feedSlice.js +3 -0
- package/lib/commonjs/core/stores/slices/feedSlice.js.map +1 -1
- package/lib/commonjs/core/stores/slices/globalfeedSlice.js +5 -0
- package/lib/commonjs/core/stores/slices/globalfeedSlice.js.map +1 -1
- package/lib/commonjs/core/stores/slices/localVideoThumbnailSlice.js +46 -0
- package/lib/commonjs/core/stores/slices/localVideoThumbnailSlice.js.map +1 -0
- package/lib/commonjs/core/stores/slices/uiSlice.js +5 -1
- package/lib/commonjs/core/stores/slices/uiSlice.js.map +1 -1
- package/lib/commonjs/core/stores/store/index.js +3 -1
- package/lib/commonjs/core/stores/store/index.js.map +1 -1
- package/lib/commonjs/social/components/LoadingImage/index.js +73 -12
- package/lib/commonjs/social/components/LoadingImage/index.js.map +1 -1
- package/lib/commonjs/social/components/LoadingImage/styles.js +8 -3
- package/lib/commonjs/social/components/LoadingImage/styles.js.map +1 -1
- package/lib/commonjs/social/components/LoadingVideo/index.js +90 -16
- package/lib/commonjs/social/components/LoadingVideo/index.js.map +1 -1
- package/lib/commonjs/social/components/LoadingVideo/styles.js +14 -5
- package/lib/commonjs/social/components/LoadingVideo/styles.js.map +1 -1
- package/lib/commonjs/social/components/MediaViewer/MediaViewer.js +13 -13
- package/lib/commonjs/social/components/MediaViewer/MediaViewer.js.map +1 -1
- package/lib/commonjs/social/components/MediaViewer/styles.js +11 -1
- package/lib/commonjs/social/components/MediaViewer/styles.js.map +1 -1
- package/lib/commonjs/social/components/PostContent/index.js +8 -29
- package/lib/commonjs/social/components/PostContent/index.js.map +1 -1
- package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js +109 -17
- package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -1
- package/lib/commonjs/social/components/legacy/LoadingVideo/index.js +17 -6
- package/lib/commonjs/social/components/legacy/LoadingVideo/index.js.map +1 -1
- package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js +121 -23
- package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js.map +1 -1
- package/lib/commonjs/social/elements/PostMediaElement/styles.js +7 -0
- package/lib/commonjs/social/elements/PostMediaElement/styles.js.map +1 -1
- package/lib/commonjs/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js +11 -5
- package/lib/commonjs/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js.map +1 -1
- package/lib/commonjs/social/features/feed/components/NewsFeed/NewsFeed.js +14 -0
- package/lib/commonjs/social/features/feed/components/NewsFeed/NewsFeed.js.map +1 -1
- package/lib/commonjs/social/features/post/Composer/PostComposer.js +234 -86
- package/lib/commonjs/social/features/post/Composer/PostComposer.js.map +1 -1
- package/lib/commonjs/social/hooks/index.js +11 -0
- package/lib/commonjs/social/hooks/index.js.map +1 -1
- package/lib/commonjs/social/hooks/useCommunities.js +5 -3
- package/lib/commonjs/social/hooks/useCommunities.js.map +1 -1
- package/lib/commonjs/social/hooks/useCommunityPermission.js +43 -0
- package/lib/commonjs/social/hooks/useCommunityPermission.js.map +1 -0
- package/lib/commonjs/social/hooks/useCustomRankingGlobalFeed.js +10 -1
- package/lib/commonjs/social/hooks/useCustomRankingGlobalFeed.js.map +1 -1
- package/lib/commonjs/social/hooks/useFile.js +11 -0
- package/lib/commonjs/social/hooks/useFile.js.map +1 -1
- package/lib/commonjs/social/screens/CommunityProfile/components/Header/Header.js +18 -7
- package/lib/commonjs/social/screens/CommunityProfile/components/Header/Header.js.map +1 -1
- package/lib/commonjs/social/utils/mediaAttachments.js +150 -0
- package/lib/commonjs/social/utils/mediaAttachments.js.map +1 -0
- package/lib/commonjs/social/utils/postChildren.js +72 -0
- package/lib/commonjs/social/utils/postChildren.js.map +1 -0
- package/lib/module/core/assets/icons/post.js +1 -1
- package/lib/module/core/assets/icons/post.js.map +1 -1
- package/lib/module/core/assets/icons/story.js +1 -1
- package/lib/module/core/assets/icons/story.js.map +1 -1
- package/lib/module/core/assets/icons/xml.js +13 -0
- package/lib/module/core/assets/icons/xml.js.map +1 -1
- package/lib/module/core/legacy/file.js +145 -62
- package/lib/module/core/legacy/file.js.map +1 -1
- package/lib/module/core/stores/slices/feedSlice.js +3 -0
- package/lib/module/core/stores/slices/feedSlice.js.map +1 -1
- package/lib/module/core/stores/slices/globalfeedSlice.js +5 -0
- package/lib/module/core/stores/slices/globalfeedSlice.js.map +1 -1
- package/lib/module/core/stores/slices/localVideoThumbnailSlice.js +41 -0
- package/lib/module/core/stores/slices/localVideoThumbnailSlice.js.map +1 -0
- package/lib/module/core/stores/slices/uiSlice.js +5 -1
- package/lib/module/core/stores/slices/uiSlice.js.map +1 -1
- package/lib/module/core/stores/store/index.js +3 -1
- package/lib/module/core/stores/store/index.js.map +1 -1
- package/lib/module/social/components/LoadingImage/index.js +74 -14
- package/lib/module/social/components/LoadingImage/index.js.map +1 -1
- package/lib/module/social/components/LoadingImage/styles.js +8 -3
- package/lib/module/social/components/LoadingImage/styles.js.map +1 -1
- package/lib/module/social/components/LoadingVideo/index.js +91 -17
- package/lib/module/social/components/LoadingVideo/index.js.map +1 -1
- package/lib/module/social/components/LoadingVideo/styles.js +14 -5
- package/lib/module/social/components/LoadingVideo/styles.js.map +1 -1
- package/lib/module/social/components/MediaViewer/MediaViewer.js +13 -13
- package/lib/module/social/components/MediaViewer/MediaViewer.js.map +1 -1
- package/lib/module/social/components/MediaViewer/styles.js +11 -1
- package/lib/module/social/components/MediaViewer/styles.js.map +1 -1
- package/lib/module/social/components/PostContent/index.js +8 -29
- package/lib/module/social/components/PostContent/index.js.map +1 -1
- package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js +110 -18
- package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -1
- package/lib/module/social/components/legacy/LoadingVideo/index.js +17 -6
- package/lib/module/social/components/legacy/LoadingVideo/index.js.map +1 -1
- package/lib/module/social/elements/PostMediaElement/PostMediaElement.js +123 -25
- package/lib/module/social/elements/PostMediaElement/PostMediaElement.js.map +1 -1
- package/lib/module/social/elements/PostMediaElement/styles.js +7 -0
- package/lib/module/social/elements/PostMediaElement/styles.js.map +1 -1
- package/lib/module/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js +12 -6
- package/lib/module/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js.map +1 -1
- package/lib/module/social/features/feed/components/NewsFeed/NewsFeed.js +15 -1
- package/lib/module/social/features/feed/components/NewsFeed/NewsFeed.js.map +1 -1
- package/lib/module/social/features/post/Composer/PostComposer.js +236 -89
- package/lib/module/social/features/post/Composer/PostComposer.js.map +1 -1
- package/lib/module/social/hooks/index.js +1 -0
- package/lib/module/social/hooks/index.js.map +1 -1
- package/lib/module/social/hooks/useCommunities.js +5 -3
- package/lib/module/social/hooks/useCommunities.js.map +1 -1
- package/lib/module/social/hooks/useCommunityPermission.js +37 -0
- package/lib/module/social/hooks/useCommunityPermission.js.map +1 -0
- package/lib/module/social/hooks/useCustomRankingGlobalFeed.js +10 -1
- package/lib/module/social/hooks/useCustomRankingGlobalFeed.js.map +1 -1
- package/lib/module/social/hooks/useFile.js +11 -0
- package/lib/module/social/hooks/useFile.js.map +1 -1
- package/lib/module/social/screens/CommunityProfile/components/Header/Header.js +19 -8
- package/lib/module/social/screens/CommunityProfile/components/Header/Header.js.map +1 -1
- package/lib/module/social/utils/mediaAttachments.js +139 -0
- package/lib/module/social/utils/mediaAttachments.js.map +1 -0
- package/lib/module/social/utils/postChildren.js +66 -0
- package/lib/module/social/utils/postChildren.js.map +1 -0
- package/lib/typescript/core/assets/icons/xml.d.ts +1 -0
- package/lib/typescript/core/assets/icons/xml.d.ts.map +1 -1
- package/lib/typescript/core/legacy/file.d.ts +32 -0
- package/lib/typescript/core/legacy/file.d.ts.map +1 -1
- package/lib/typescript/core/stores/slices/feedSlice.d.ts.map +1 -1
- package/lib/typescript/core/stores/slices/globalfeedSlice.d.ts.map +1 -1
- package/lib/typescript/core/stores/slices/localVideoThumbnailSlice.d.ts +35 -0
- package/lib/typescript/core/stores/slices/localVideoThumbnailSlice.d.ts.map +1 -0
- package/lib/typescript/core/stores/slices/uiSlice.d.ts +5 -0
- package/lib/typescript/core/stores/slices/uiSlice.d.ts.map +1 -1
- package/lib/typescript/core/stores/store/index.d.ts.map +1 -1
- package/lib/typescript/core/types/mention.d.ts +34 -0
- package/lib/typescript/core/types/mention.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingImage/index.d.ts +8 -2
- package/lib/typescript/social/components/LoadingImage/index.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingImage/styles.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingVideo/index.d.ts +9 -2
- package/lib/typescript/social/components/LoadingVideo/index.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingVideo/styles.d.ts +1 -0
- package/lib/typescript/social/components/LoadingVideo/styles.d.ts.map +1 -1
- package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts.map +1 -1
- package/lib/typescript/social/components/MediaViewer/styles.d.ts +1 -0
- package/lib/typescript/social/components/MediaViewer/styles.d.ts.map +1 -1
- package/lib/typescript/social/components/PostContent/index.d.ts.map +1 -1
- package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts +2 -2
- package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts.map +1 -1
- package/lib/typescript/social/components/legacy/LoadingVideo/index.d.ts.map +1 -1
- package/lib/typescript/social/elements/PostMediaElement/PostMediaElement.d.ts.map +1 -1
- package/lib/typescript/social/elements/PostMediaElement/styles.d.ts +4 -0
- package/lib/typescript/social/elements/PostMediaElement/styles.d.ts.map +1 -1
- package/lib/typescript/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.d.ts.map +1 -1
- package/lib/typescript/social/features/feed/components/NewsFeed/NewsFeed.d.ts.map +1 -1
- package/lib/typescript/social/features/post/Composer/PostComposer.d.ts.map +1 -1
- package/lib/typescript/social/hooks/index.d.ts +1 -0
- package/lib/typescript/social/hooks/index.d.ts.map +1 -1
- package/lib/typescript/social/hooks/useCommunities.d.ts +2 -1
- package/lib/typescript/social/hooks/useCommunities.d.ts.map +1 -1
- package/lib/typescript/social/hooks/useCommunityPermission.d.ts +14 -0
- package/lib/typescript/social/hooks/useCommunityPermission.d.ts.map +1 -0
- package/lib/typescript/social/hooks/useCustomRankingGlobalFeed.d.ts.map +1 -1
- package/lib/typescript/social/hooks/useFile.d.ts +11 -0
- package/lib/typescript/social/hooks/useFile.d.ts.map +1 -1
- package/lib/typescript/social/screens/CommunityProfile/components/Header/Header.d.ts.map +1 -1
- package/lib/typescript/social/utils/mediaAttachments.d.ts +91 -0
- package/lib/typescript/social/utils/mediaAttachments.d.ts.map +1 -0
- package/lib/typescript/social/utils/postChildren.d.ts +38 -0
- package/lib/typescript/social/utils/postChildren.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/core/assets/icons/post.tsx +1 -1
- package/src/core/assets/icons/story.tsx +1 -1
- package/src/core/assets/icons/xml.ts +15 -0
- package/src/core/legacy/file.tsx +169 -84
- package/src/core/stores/slices/feedSlice.ts +3 -0
- package/src/core/stores/slices/globalfeedSlice.ts +5 -0
- package/src/core/stores/slices/localVideoThumbnailSlice.ts +58 -0
- package/src/core/stores/slices/uiSlice.ts +8 -0
- package/src/core/stores/store/index.ts +2 -0
- package/src/core/types/mention.ts +34 -0
- package/src/social/components/LoadingImage/index.tsx +81 -12
- package/src/social/components/LoadingImage/styles.ts +8 -3
- package/src/social/components/LoadingVideo/index.tsx +111 -21
- package/src/social/components/LoadingVideo/styles.ts +13 -4
- package/src/social/components/MediaViewer/MediaViewer.tsx +11 -12
- package/src/social/components/MediaViewer/styles.ts +9 -1
- package/src/social/components/PostContent/index.tsx +3 -35
- package/src/social/components/SelectedMediaComponent/SelectedMediaComponent.tsx +103 -13
- package/src/social/components/legacy/LoadingVideo/index.tsx +26 -15
- package/src/social/elements/PostMediaElement/PostMediaElement.tsx +139 -29
- package/src/social/elements/PostMediaElement/styles.ts +7 -0
- package/src/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.tsx +11 -6
- package/src/social/features/feed/components/NewsFeed/NewsFeed.tsx +16 -1
- package/src/social/features/post/Composer/PostComposer.tsx +304 -91
- package/src/social/hooks/index.ts +1 -0
- package/src/social/hooks/useCommunities.ts +4 -2
- package/src/social/hooks/useCommunityPermission.ts +40 -0
- package/src/social/hooks/useCustomRankingGlobalFeed.ts +8 -1
- package/src/social/hooks/useFile.ts +11 -0
- package/src/social/screens/CommunityProfile/components/Header/Header.tsx +19 -10
- package/src/social/utils/mediaAttachments.ts +158 -0
- 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
|
-
|
|
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 <=
|
|
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
|
-
//
|
|
149
|
-
//
|
|
150
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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,
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
}, [
|
|
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 (
|
|
209
|
-
|
|
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
|
|
388
|
-
if (mediaType === 'video' && displayVideos.length
|
|
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
|
-
|
|
418
|
-
|
|
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
|
-
}, [
|
|
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 >=
|
|
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:
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
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,
|
|
672
|
+
const handleOnFinishImage = useCallback((fileId, fileUrl, fileName, _index, originalPath) => {
|
|
516
673
|
setHasChangedAttachment(true);
|
|
517
|
-
|
|
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,
|
|
681
|
+
const handleOnFinishVideo = useCallback((fileId, fileUrl, fileName, _index, originalPath, thumbnail) => {
|
|
530
682
|
setHasChangedAttachment(true);
|
|
531
|
-
|
|
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 >=
|
|
694
|
+
const isMediaCapReached = displayImages.length >= MAX_MEDIA_ATTACHMENTS || displayVideos.length >= MAX_MEDIA_ATTACHMENTS;
|
|
548
695
|
const renderDetailedAttachment = useCallback(() => {
|
|
549
|
-
if (
|
|
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:
|
|
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:
|
|
711
|
+
chosenMediaType: activeMediaType,
|
|
565
712
|
onHeightChange: setAttachmentBarHeight,
|
|
566
713
|
disabled: isMediaCapReached
|
|
567
714
|
});
|
|
568
|
-
}, [
|
|
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
|
-
|
|
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
|
-
|
|
770
|
+
onUploadingChange: handleUploadingChange
|
|
624
771
|
}))), renderSuggestions({
|
|
625
772
|
type: 'post'
|
|
626
773
|
}), /*#__PURE__*/React.createElement(View, {
|