@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
|
@@ -11,6 +11,40 @@ export interface IDisplayImage {
|
|
|
11
11
|
fileId: string | undefined;
|
|
12
12
|
fileName: string;
|
|
13
13
|
isUploaded: boolean;
|
|
14
|
+
/** Stable local identity of a staged attachment, assigned once when the item
|
|
15
|
+
* is created (picker asset id / original file name / capture path) and
|
|
16
|
+
* carried unchanged through the upload. `url` and `fileName` are both
|
|
17
|
+
* rewritten the moment the upload finishes — the url becomes the remote
|
|
18
|
+
* `?size=medium` one and fileName becomes the SERVER's name — so neither can
|
|
19
|
+
* identify an item across its own upload. Two defects come out of that:
|
|
20
|
+
* de-duplicating a second pick against already-uploaded items compared local
|
|
21
|
+
* names to server names and never matched, and the completion
|
|
22
|
+
* handler had to fall back to the positional `index` it was rendered with,
|
|
23
|
+
* which lands on the wrong entry once the array's composition has moved
|
|
24
|
+
* underneath an in-flight upload. Optional because the legacy
|
|
25
|
+
* EditPostModal builds these objects too. */
|
|
26
|
+
localId?: string;
|
|
27
|
+
/** The local file path the item was picked or captured from, kept for
|
|
28
|
+
* DISPLAY only. `url` is replaced by the remote `?size=medium` url the
|
|
29
|
+
* instant the upload finishes, so the composer frame stops rendering the
|
|
30
|
+
* file on disk and starts fetching over the network — go offline and the
|
|
31
|
+
* already-uploaded frame turns grey, even though the local file
|
|
32
|
+
* is still sitting there. Rendering from this instead keeps the frame
|
|
33
|
+
* painted whatever the network is doing. Absent for edit-mode children,
|
|
34
|
+
* which have no local file, so callers fall back to `url`. */
|
|
35
|
+
localUrl?: string;
|
|
36
|
+
/** The picker's original `asset.fileName` for a library pick, kept as the
|
|
37
|
+
* secondary de-duplication key next to `localId`: `asset.id` is
|
|
38
|
+
* only populated on some picker configurations, so the name is what catches
|
|
39
|
+
* a re-pick when it is absent. Left unset for camera captures, whose temp
|
|
40
|
+
* path is unique per shot and must never de-duplicate against anything. */
|
|
41
|
+
localFileName?: string;
|
|
14
42
|
thumbNail?: string;
|
|
15
43
|
postId?: string;
|
|
44
|
+
/** Source dimensions used to classify the composer frame ratio. For video
|
|
45
|
+
* these are the STORED frame; `rotation` must be applied before classifying
|
|
46
|
+
* (SelectedMediaComponent REQ-003d1, AmityVideo REQ-SDK-002). */
|
|
47
|
+
width?: number;
|
|
48
|
+
height?: number;
|
|
49
|
+
rotation?: number;
|
|
16
50
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
2
3
|
import { View, Image, TouchableOpacity } from 'react-native';
|
|
3
4
|
import * as Progress from 'react-native-progress';
|
|
4
5
|
import { SvgXml } from 'react-native-svg';
|
|
5
6
|
import { deleteAmityFile, uploadImageFile } from '../../../core/legacy/file';
|
|
6
|
-
import {
|
|
7
|
+
import { mediaRemoveIcon, toastIcon } from '../../../core/assets/icons/xml';
|
|
7
8
|
import { useStyles } from './styles';
|
|
8
9
|
|
|
9
10
|
interface OverlayImageProps {
|
|
@@ -19,11 +20,21 @@ interface OverlayImageProps {
|
|
|
19
20
|
onUploadError?: (hasError: boolean, source: string) => void;
|
|
20
21
|
index?: number;
|
|
21
22
|
isUploaded: boolean;
|
|
23
|
+
/** Local path to render from, when the item still has one. `source` stays
|
|
24
|
+
* the identity/bookkeeping key and becomes the remote url once the upload
|
|
25
|
+
* finishes; painting from that url means an already-uploaded frame goes
|
|
26
|
+
* grey the moment the device is offline. Falls back to `source`
|
|
27
|
+
* for edit-mode children, which have no local file. */
|
|
28
|
+
displayUri?: string;
|
|
22
29
|
fileId?: string;
|
|
23
30
|
isEditMode?: boolean;
|
|
24
31
|
fileCount?: number;
|
|
25
32
|
postId?: string;
|
|
26
|
-
|
|
33
|
+
// Reports this frame's upload state keyed by its own `source`, so the
|
|
34
|
+
// composer can gate Post on every frame at once. The old shared
|
|
35
|
+
// `setIsUploading` boolean was flipped back to false by whichever upload
|
|
36
|
+
// finished first, unlocking Post while the other frames were still in flight.
|
|
37
|
+
onUploadingChange?: (isUploading: boolean, source: string) => void;
|
|
27
38
|
carousel?: boolean;
|
|
28
39
|
}
|
|
29
40
|
const LoadingImage = ({
|
|
@@ -33,11 +44,12 @@ const LoadingImage = ({
|
|
|
33
44
|
onLoadFinish,
|
|
34
45
|
onUploadError,
|
|
35
46
|
isUploaded = false,
|
|
47
|
+
displayUri,
|
|
36
48
|
fileId = '',
|
|
37
49
|
isEditMode = false,
|
|
38
50
|
fileCount,
|
|
39
51
|
postId,
|
|
40
|
-
|
|
52
|
+
onUploadingChange,
|
|
41
53
|
carousel = false,
|
|
42
54
|
}: OverlayImageProps) => {
|
|
43
55
|
const [loading, setLoading] = useState(true);
|
|
@@ -47,8 +59,8 @@ const LoadingImage = ({
|
|
|
47
59
|
const styles = useStyles();
|
|
48
60
|
const handleLoadEnd = useCallback(() => {
|
|
49
61
|
setLoading(false);
|
|
50
|
-
|
|
51
|
-
}, [
|
|
62
|
+
onUploadingChange?.(false, source);
|
|
63
|
+
}, [onUploadingChange, source]);
|
|
52
64
|
|
|
53
65
|
useEffect(() => {
|
|
54
66
|
if (progress === 100) {
|
|
@@ -57,8 +69,25 @@ const LoadingImage = ({
|
|
|
57
69
|
}, [progress]);
|
|
58
70
|
|
|
59
71
|
const uploadFileToAmity = useCallback(async () => {
|
|
60
|
-
|
|
72
|
+
onUploadingChange?.(true, source);
|
|
61
73
|
setIsUploadError(false);
|
|
74
|
+
// A retry re-enters this function with `loading` already false — the failed
|
|
75
|
+
// attempt's `handleLoadEnd` cleared it and nothing ever set it back, so
|
|
76
|
+
// `setLoading` only ever ran downwards in this component. The frame then
|
|
77
|
+
// re-uploaded with no spinner at all, which reads as "finished instantly"
|
|
78
|
+
// while Post stays disabled for the length of the upload.
|
|
79
|
+
// Progress and the processing flag are stale from the failed attempt too,
|
|
80
|
+
// so reset the whole visual upload state here rather than only in the
|
|
81
|
+
// mount effect, which does not re-run on a retry.
|
|
82
|
+
setLoading(true);
|
|
83
|
+
setProgress(0);
|
|
84
|
+
setIsProcess(false);
|
|
85
|
+
// Clearing the local flag alone left this source inside the parent's
|
|
86
|
+
// `imageErrors` set, and that set is otherwise only cleared by the
|
|
87
|
+
// mount effect below — which does not re-run on a retry, since neither
|
|
88
|
+
// `isUploaded` nor `source` changed. Post stayed disabled even after the
|
|
89
|
+
// retry uploaded fine, so tell the parent the error is gone up front.
|
|
90
|
+
onUploadError?.(false, source);
|
|
62
91
|
try {
|
|
63
92
|
const file: Amity.File<any>[] = await uploadImageFile(
|
|
64
93
|
source,
|
|
@@ -78,7 +107,8 @@ const LoadingImage = ({
|
|
|
78
107
|
source
|
|
79
108
|
);
|
|
80
109
|
} else {
|
|
81
|
-
|
|
110
|
+
// `handleLoadEnd` already reports the upload as finished — the extra
|
|
111
|
+
// setter call it used to be paired with was redundant.
|
|
82
112
|
handleLoadEnd();
|
|
83
113
|
setIsProcess(false);
|
|
84
114
|
setIsUploadError(true);
|
|
@@ -87,7 +117,6 @@ const LoadingImage = ({
|
|
|
87
117
|
} catch (error) {
|
|
88
118
|
handleLoadEnd();
|
|
89
119
|
setIsProcess(false);
|
|
90
|
-
setIsUploading(false);
|
|
91
120
|
setIsUploadError(true);
|
|
92
121
|
onUploadError?.(true, source);
|
|
93
122
|
}
|
|
@@ -96,10 +125,34 @@ const LoadingImage = ({
|
|
|
96
125
|
index,
|
|
97
126
|
onLoadFinish,
|
|
98
127
|
onUploadError,
|
|
99
|
-
|
|
128
|
+
onUploadingChange,
|
|
100
129
|
source,
|
|
101
130
|
]);
|
|
102
131
|
|
|
132
|
+
// The frame is expected to end up uploaded, and Post enabled, once
|
|
133
|
+
// connectivity returns. Nothing retried on its own —
|
|
134
|
+
// the error key only cleared on a manual tap on that specific frame, and the
|
|
135
|
+
// peek carousel can leave a failed frame off-screen entirely, so Post stayed
|
|
136
|
+
// disabled with no visible cause. Retry when the device regains a
|
|
137
|
+
// connection, using the same NetInfo listener idiom as post Detail and the
|
|
138
|
+
// livestream screens.
|
|
139
|
+
//
|
|
140
|
+
// Gated on a disconnected -> connected transition, not merely on being
|
|
141
|
+
// connected: addEventListener fires immediately with the current state, so a
|
|
142
|
+
// failure that happened while online (a server error, say) would otherwise
|
|
143
|
+
// re-fire on every resubscribe and spin.
|
|
144
|
+
const wasDisconnectedRef = useRef(false);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
const unsubscribe = NetInfo.addEventListener((state) => {
|
|
147
|
+
const isConnected = !!state.isConnected;
|
|
148
|
+
if (isConnected && wasDisconnectedRef.current && isUploadError) {
|
|
149
|
+
uploadFileToAmity();
|
|
150
|
+
}
|
|
151
|
+
wasDisconnectedRef.current = !isConnected;
|
|
152
|
+
});
|
|
153
|
+
return () => unsubscribe();
|
|
154
|
+
}, [isUploadError, uploadFileToAmity]);
|
|
155
|
+
|
|
103
156
|
const handleDelete = async () => {
|
|
104
157
|
if (fileId && !isEditMode) {
|
|
105
158
|
await deleteAmityFile(fileId);
|
|
@@ -118,6 +171,17 @@ const LoadingImage = ({
|
|
|
118
171
|
}
|
|
119
172
|
}, [isUploaded, source]);
|
|
120
173
|
|
|
174
|
+
// A frame can be removed (or its source swapped) while its upload is still
|
|
175
|
+
// in flight, in which case `handleLoadEnd` never runs and the composer would
|
|
176
|
+
// keep waiting on an entry no mounted child owns any more — leaving Post
|
|
177
|
+
// disabled forever. Clearing it from this cleanup keeps the
|
|
178
|
+
// bookkeeping in the same component that added it.
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
return () => {
|
|
181
|
+
onUploadingChange?.(false, source);
|
|
182
|
+
};
|
|
183
|
+
}, [onUploadingChange, source]);
|
|
184
|
+
|
|
121
185
|
const onRetryUpload = () => {
|
|
122
186
|
uploadFileToAmity();
|
|
123
187
|
};
|
|
@@ -132,7 +196,7 @@ const LoadingImage = ({
|
|
|
132
196
|
}
|
|
133
197
|
>
|
|
134
198
|
<Image
|
|
135
|
-
source={{ uri: source }}
|
|
199
|
+
source={{ uri: displayUri ?? source }}
|
|
136
200
|
resizeMode={carousel ? 'cover' : 'contain'}
|
|
137
201
|
style={[
|
|
138
202
|
styles.image,
|
|
@@ -168,10 +232,15 @@ const LoadingImage = ({
|
|
|
168
232
|
|
|
169
233
|
<TouchableOpacity
|
|
170
234
|
style={styles.closeButton}
|
|
235
|
+
// The button matches web at 28dp, which is still under Apple's 44pt
|
|
236
|
+
// minimum touch target. Grow the touch area rather than the black
|
|
237
|
+
// disc, so the visual stays in parity while the frame stays tappable:
|
|
238
|
+
// 28 + 8 on each side lands exactly on 44.
|
|
239
|
+
hitSlop={8}
|
|
171
240
|
disabled={(loading || isProcess) && !isUploadError}
|
|
172
241
|
onPress={handleDelete}
|
|
173
242
|
>
|
|
174
|
-
<SvgXml xml={
|
|
243
|
+
<SvgXml xml={mediaRemoveIcon()} width="20" height="20" />
|
|
175
244
|
</TouchableOpacity>
|
|
176
245
|
</View>
|
|
177
246
|
);
|
|
@@ -55,9 +55,14 @@ export const useStyles = () => {
|
|
|
55
55
|
},
|
|
56
56
|
closeButton: {
|
|
57
57
|
position: 'absolute',
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
// Matched to the web UIKit's remove button, which is 20px of icon inside
|
|
59
|
+
// 4px of padding at an 8px offset. The old 12/7 pair came to nearly the
|
|
60
|
+
// same 26dp outer size but filled only 46% of the circle with the glyph
|
|
61
|
+
// against web's 71%, so it read as a heavy black disc with a tiny cross
|
|
62
|
+
// in it.
|
|
63
|
+
top: 8,
|
|
64
|
+
right: 8,
|
|
65
|
+
padding: 4,
|
|
61
66
|
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
62
67
|
borderRadius: 72,
|
|
63
68
|
zIndex: 10,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
2
3
|
import {
|
|
3
4
|
View,
|
|
4
5
|
TouchableOpacity,
|
|
@@ -10,7 +11,7 @@ import * as Progress from 'react-native-progress';
|
|
|
10
11
|
import { SvgXml } from 'react-native-svg';
|
|
11
12
|
import { deleteAmityFile, uploadVideoFile } from '../../../core/legacy/file';
|
|
12
13
|
import {
|
|
13
|
-
|
|
14
|
+
mediaRemoveIcon,
|
|
14
15
|
playBtn,
|
|
15
16
|
toastIcon,
|
|
16
17
|
videoControlIcon,
|
|
@@ -25,6 +26,13 @@ import { useUIKitDispatch } from '../../../core/stores/store';
|
|
|
25
26
|
|
|
26
27
|
interface OverlayImageProps {
|
|
27
28
|
source: string;
|
|
29
|
+
/** Local path to render/decode from, when the item still has one. `source`
|
|
30
|
+
* stays the identity/bookkeeping key and becomes the remote url once the
|
|
31
|
+
* upload finishes; decoding the thumbnail from that url means an
|
|
32
|
+
* already-uploaded frame goes grey the moment the device is offline
|
|
33
|
+
* Falls back to `source` for edit-mode children, which have no
|
|
34
|
+
* local file. */
|
|
35
|
+
displayUri?: string;
|
|
28
36
|
onClose?: (originalPath: string, fileId?: string, postId?: string) => void;
|
|
29
37
|
onLoadFinish?: (
|
|
30
38
|
fileId: string,
|
|
@@ -43,7 +51,11 @@ interface OverlayImageProps {
|
|
|
43
51
|
isEditMode?: boolean;
|
|
44
52
|
fileCount?: number;
|
|
45
53
|
postId?: string;
|
|
46
|
-
|
|
54
|
+
// Reports this frame's upload state keyed by its own `source`, so the
|
|
55
|
+
// composer can gate Post on every frame at once. The old shared
|
|
56
|
+
// `setIsUploading` boolean was flipped back to false by whichever upload
|
|
57
|
+
// finished first, unlocking Post while the other frames were still in flight.
|
|
58
|
+
onUploadingChange?: (isUploading: boolean, source: string) => void;
|
|
47
59
|
carousel?: boolean;
|
|
48
60
|
}
|
|
49
61
|
const LoadingVideo = ({
|
|
@@ -53,13 +65,14 @@ const LoadingVideo = ({
|
|
|
53
65
|
onLoadFinish,
|
|
54
66
|
onUploadError,
|
|
55
67
|
isUploaded = false,
|
|
68
|
+
displayUri,
|
|
56
69
|
thumbNail,
|
|
57
70
|
onPlay,
|
|
58
71
|
fileId,
|
|
59
72
|
isEditMode = false,
|
|
60
73
|
fileCount,
|
|
61
74
|
postId,
|
|
62
|
-
|
|
75
|
+
onUploadingChange,
|
|
63
76
|
carousel = false,
|
|
64
77
|
}: OverlayImageProps) => {
|
|
65
78
|
const dispatch = useUIKitDispatch();
|
|
@@ -69,6 +82,10 @@ const LoadingVideo = ({
|
|
|
69
82
|
const [isProcess, setIsProcess] = useState<boolean>(false);
|
|
70
83
|
const [isUploadError, setIsUploadError] = useState(false);
|
|
71
84
|
const [thumbNailImage, setThumbNailImage] = useState(thumbNail ?? '');
|
|
85
|
+
// `uploadFileToAmity` is memoised on [source] only, so reading the state
|
|
86
|
+
// there would capture the empty first-render value. Mirror it into a ref so
|
|
87
|
+
// the upload callback always sees the frame that was actually decoded.
|
|
88
|
+
const thumbNailImageRef = useRef(thumbNail ?? '');
|
|
72
89
|
const styles = useStyles();
|
|
73
90
|
const [playingUri, setPlayingUri] = useState<string>('');
|
|
74
91
|
const [isPause, setIsPause] = useState<boolean>(true);
|
|
@@ -89,11 +106,12 @@ const LoadingVideo = ({
|
|
|
89
106
|
|
|
90
107
|
const handleLoadEnd = () => {
|
|
91
108
|
setLoading(false);
|
|
92
|
-
|
|
109
|
+
onUploadingChange?.(false, source);
|
|
93
110
|
};
|
|
94
111
|
|
|
95
112
|
const processThumbNail = async () => {
|
|
96
|
-
const generatedThumbNail = await createVideoThumbnail(source);
|
|
113
|
+
const generatedThumbNail = await createVideoThumbnail(displayUri ?? source);
|
|
114
|
+
thumbNailImageRef.current = generatedThumbNail.path;
|
|
97
115
|
setThumbNailImage(generatedThumbNail.path);
|
|
98
116
|
};
|
|
99
117
|
useEffect(() => {
|
|
@@ -107,8 +125,25 @@ const LoadingVideo = ({
|
|
|
107
125
|
}, [progress]);
|
|
108
126
|
|
|
109
127
|
const uploadFileToAmity = useCallback(async () => {
|
|
110
|
-
|
|
128
|
+
onUploadingChange?.(true, source);
|
|
111
129
|
setIsUploadError(false);
|
|
130
|
+
// A retry re-enters this function with `loading` already false — the failed
|
|
131
|
+
// attempt's `handleLoadEnd` cleared it and nothing ever set it back, so
|
|
132
|
+
// `setLoading` only ever ran downwards in this component. The frame then
|
|
133
|
+
// re-uploaded with no spinner at all, which reads as "finished instantly"
|
|
134
|
+
// while Post stays disabled for the length of the upload.
|
|
135
|
+
// Progress and the processing flag are stale from the failed attempt too,
|
|
136
|
+
// so reset the whole visual upload state here rather than only in the
|
|
137
|
+
// mount effect, which does not re-run on a retry.
|
|
138
|
+
setLoading(true);
|
|
139
|
+
setProgress(0);
|
|
140
|
+
setIsProcess(false);
|
|
141
|
+
// Clearing the local flag alone left this source inside the parent's
|
|
142
|
+
// `videoErrors` set, and that set is otherwise only cleared by the
|
|
143
|
+
// mount effect below — which does not re-run on a retry, since none of
|
|
144
|
+
// `fileId`/`isUploaded`/`source` changed. Post stayed disabled even after
|
|
145
|
+
// the retry uploaded fine, so tell the parent the error is gone up front.
|
|
146
|
+
onUploadError?.(false, source);
|
|
112
147
|
try {
|
|
113
148
|
const file: Amity.File<any>[] = await uploadVideoFile(
|
|
114
149
|
source,
|
|
@@ -126,7 +161,12 @@ const LoadingVideo = ({
|
|
|
126
161
|
file[0]?.attributes.name as string,
|
|
127
162
|
index as number,
|
|
128
163
|
source,
|
|
129
|
-
|
|
164
|
+
// Hand back the frame this component decoded, not the incoming
|
|
165
|
+
// prop — for a newly picked video that prop is empty, so the
|
|
166
|
+
// generated thumbnail used to be dropped on the floor. The
|
|
167
|
+
// composer stores it so the feed can show it while the server is
|
|
168
|
+
// still transcoding (PDT-4904).
|
|
169
|
+
thumbNailImageRef.current || thumbNail
|
|
130
170
|
);
|
|
131
171
|
} else {
|
|
132
172
|
handleLoadEnd();
|
|
@@ -144,6 +184,30 @@ const LoadingVideo = ({
|
|
|
144
184
|
}
|
|
145
185
|
}, [source]);
|
|
146
186
|
|
|
187
|
+
// The frame is expected to end up uploaded, and Post enabled, once
|
|
188
|
+
// connectivity returns. Nothing retried on its own —
|
|
189
|
+
// the error key only cleared on a manual tap on that specific frame, and the
|
|
190
|
+
// peek carousel can leave a failed frame off-screen entirely, so Post stayed
|
|
191
|
+
// disabled with no visible cause. Retry when the device regains a
|
|
192
|
+
// connection, using the same NetInfo listener idiom as post Detail and the
|
|
193
|
+
// livestream screens.
|
|
194
|
+
//
|
|
195
|
+
// Gated on a disconnected -> connected transition, not merely on being
|
|
196
|
+
// connected: addEventListener fires immediately with the current state, so a
|
|
197
|
+
// failure that happened while online (a server error, say) would otherwise
|
|
198
|
+
// re-fire on every resubscribe and spin.
|
|
199
|
+
const wasDisconnectedRef = useRef(false);
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
const unsubscribe = NetInfo.addEventListener((state) => {
|
|
202
|
+
const isConnected = !!state.isConnected;
|
|
203
|
+
if (isConnected && wasDisconnectedRef.current && isUploadError) {
|
|
204
|
+
uploadFileToAmity();
|
|
205
|
+
}
|
|
206
|
+
wasDisconnectedRef.current = !isConnected;
|
|
207
|
+
});
|
|
208
|
+
return () => unsubscribe();
|
|
209
|
+
}, [isUploadError, uploadFileToAmity]);
|
|
210
|
+
|
|
147
211
|
const handleDelete = async () => {
|
|
148
212
|
if (fileId && !isEditMode) {
|
|
149
213
|
await deleteAmityFile(fileId);
|
|
@@ -163,16 +227,30 @@ const LoadingVideo = ({
|
|
|
163
227
|
}, [fileId, isUploaded, source]);
|
|
164
228
|
|
|
165
229
|
const handleOnPlay = () => {
|
|
166
|
-
|
|
167
|
-
//
|
|
168
|
-
//
|
|
230
|
+
// In carousel mode the parent (onPlay) owns the full-screen media viewer,
|
|
231
|
+
// so this component never mounts the inline <Video> and never receives a
|
|
232
|
+
// dismiss callback to unpause with. Leaving `isPause` alone keeps the play
|
|
233
|
+
// icon rendered, which is what the frame shows once the viewer closes
|
|
234
|
+
// (PDT-4904); toggling it here hid the icon for the rest of the session.
|
|
169
235
|
if (onPlay) {
|
|
170
236
|
onPlay(source);
|
|
171
237
|
return;
|
|
172
238
|
}
|
|
239
|
+
setIsPause(!isPause);
|
|
173
240
|
playVideoFullScreen(source);
|
|
174
241
|
};
|
|
175
242
|
|
|
243
|
+
// A frame can be removed (or its source swapped) while its upload is still
|
|
244
|
+
// in flight, in which case `handleLoadEnd` never runs and the composer would
|
|
245
|
+
// keep waiting on an entry no mounted child owns any more — leaving Post
|
|
246
|
+
// disabled forever. Clearing it from this cleanup keeps the
|
|
247
|
+
// bookkeeping in the same component that added it.
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
return () => {
|
|
250
|
+
onUploadingChange?.(false, source);
|
|
251
|
+
};
|
|
252
|
+
}, [onUploadingChange, source]);
|
|
253
|
+
|
|
176
254
|
const onRetryUpload = () => {
|
|
177
255
|
uploadFileToAmity();
|
|
178
256
|
};
|
|
@@ -217,7 +295,7 @@ const LoadingVideo = ({
|
|
|
217
295
|
<View style={styles.image} />
|
|
218
296
|
)}
|
|
219
297
|
|
|
220
|
-
{loading
|
|
298
|
+
{loading && (
|
|
221
299
|
<View style={styles.overlay}>
|
|
222
300
|
{isProcess ? (
|
|
223
301
|
<Progress.CircleSnail
|
|
@@ -235,19 +313,31 @@ const LoadingVideo = ({
|
|
|
235
313
|
/>
|
|
236
314
|
)}
|
|
237
315
|
</View>
|
|
238
|
-
)
|
|
316
|
+
)}
|
|
317
|
+
{!loading && isUploadError && (
|
|
239
318
|
<TouchableOpacity style={styles.overlay} onPress={onRetryUpload}>
|
|
240
319
|
<SvgXml xml={toastIcon()} width="24" height="24" />
|
|
241
320
|
</TouchableOpacity>
|
|
242
|
-
) : (
|
|
243
|
-
<TouchableOpacity
|
|
244
|
-
style={styles.closeButton}
|
|
245
|
-
disabled={(loading || isProcess) && !isUploadError}
|
|
246
|
-
onPress={handleDelete}
|
|
247
|
-
>
|
|
248
|
-
<SvgXml xml={closeIcon('white')} width="12" height="12" />
|
|
249
|
-
</TouchableOpacity>
|
|
250
321
|
)}
|
|
322
|
+
|
|
323
|
+
{/* Sibling of the overlays, never an `else` branch of them: a failed
|
|
324
|
+
frame must keep its remove button, otherwise a video that cannot
|
|
325
|
+
upload can never be taken out of the composer. This is
|
|
326
|
+
the shape LoadingImage already uses, and it is what the `disabled`
|
|
327
|
+
guard below was written for — inside the old if/else chain that
|
|
328
|
+
`!isUploadError` term was unreachable. */}
|
|
329
|
+
<TouchableOpacity
|
|
330
|
+
style={styles.closeButton}
|
|
331
|
+
// The button matches web at 28dp, which is still under Apple's 44pt
|
|
332
|
+
// minimum touch target. Grow the touch area rather than the black
|
|
333
|
+
// disc, so the visual stays in parity while the frame stays tappable:
|
|
334
|
+
// 28 + 8 on each side lands exactly on 44.
|
|
335
|
+
hitSlop={8}
|
|
336
|
+
disabled={(loading || isProcess) && !isUploadError}
|
|
337
|
+
onPress={handleDelete}
|
|
338
|
+
>
|
|
339
|
+
<SvgXml xml={mediaRemoveIcon()} width="20" height="20" />
|
|
340
|
+
</TouchableOpacity>
|
|
251
341
|
</View>
|
|
252
342
|
);
|
|
253
343
|
};
|
|
@@ -41,11 +41,20 @@ export const useStyles = () => {
|
|
|
41
41
|
},
|
|
42
42
|
closeButton: {
|
|
43
43
|
position: 'absolute',
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
// Matched to the web UIKit's remove button, which is 20px of icon inside
|
|
45
|
+
// 4px of padding at an 8px offset. The old 12/7 pair came to nearly the
|
|
46
|
+
// same 26dp outer size but filled only 46% of the circle with the glyph
|
|
47
|
+
// against web's 71%, so it read as a heavy black disc with a tiny cross
|
|
48
|
+
// in it.
|
|
49
|
+
top: 8,
|
|
50
|
+
right: 8,
|
|
51
|
+
padding: 4,
|
|
52
|
+
// Matches LoadingImage exactly: the icon is closeIcon('white') in both,
|
|
53
|
+
// so the white scrim this used to carry left a white-on-white button
|
|
54
|
+
// that all but disappeared over a light frame.
|
|
55
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
48
56
|
borderRadius: 72,
|
|
57
|
+
zIndex: 10,
|
|
49
58
|
},
|
|
50
59
|
playButton: {
|
|
51
60
|
width: 100,
|
|
@@ -69,11 +69,12 @@ export function MediaViewer({
|
|
|
69
69
|
const listRef = useRef<FlatList<MediaViewerItem>>(null);
|
|
70
70
|
const videoRef = useRef<VideoRef | null>(null);
|
|
71
71
|
const [current, setCurrent] = useState(initialIndex);
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
72
|
+
// The sound setting belongs to the viewing session, not to one frame: the
|
|
73
|
+
// first video opens unmuted and swiping carries whatever the viewer last
|
|
74
|
+
// chose. Re-defaulting each new frame to muted meant a swipe silenced a
|
|
75
|
+
// session the viewer never muted, since opening unmuted is a default rather
|
|
76
|
+
// than an explicit choice.
|
|
75
77
|
const [muted, setMuted] = useState(false);
|
|
76
|
-
const keepUnmutedRef = useRef(false);
|
|
77
78
|
|
|
78
79
|
// --- playback controls (ported from web VideoPlayerControls) ---
|
|
79
80
|
const [userPaused, setUserPaused] = useState(false);
|
|
@@ -118,7 +119,6 @@ export function MediaViewer({
|
|
|
118
119
|
}
|
|
119
120
|
setCurrent(initialIndex);
|
|
120
121
|
setMuted(false);
|
|
121
|
-
keepUnmutedRef.current = false;
|
|
122
122
|
resetPlayback();
|
|
123
123
|
revealControls();
|
|
124
124
|
}, [visible, initialIndex, resetPlayback, revealControls, clearHideTimer]);
|
|
@@ -129,7 +129,6 @@ export function MediaViewer({
|
|
|
129
129
|
const idx = Math.round(e.nativeEvent.contentOffset.x / width);
|
|
130
130
|
if (idx !== current) {
|
|
131
131
|
setCurrent(idx);
|
|
132
|
-
setMuted(!keepUnmutedRef.current);
|
|
133
132
|
resetPlayback();
|
|
134
133
|
revealControls();
|
|
135
134
|
}
|
|
@@ -138,11 +137,7 @@ export function MediaViewer({
|
|
|
138
137
|
);
|
|
139
138
|
|
|
140
139
|
const toggleMute = useCallback(() => {
|
|
141
|
-
setMuted((prev) =>
|
|
142
|
-
const next = !prev;
|
|
143
|
-
keepUnmutedRef.current = !next; // user unmuted -> keep unmuted afterwards
|
|
144
|
-
return next;
|
|
145
|
-
});
|
|
140
|
+
setMuted((prev) => !prev);
|
|
146
141
|
revealControls();
|
|
147
142
|
}, [revealControls]);
|
|
148
143
|
|
|
@@ -297,7 +292,11 @@ export function MediaViewer({
|
|
|
297
292
|
style={styles.media}
|
|
298
293
|
resizeMode="contain"
|
|
299
294
|
paused={index !== current || !visible || userPaused}
|
|
300
|
-
|
|
295
|
+
// Only the frame on screen may be audible. `paused` alone is
|
|
296
|
+
// not a reliable gag on an off-screen native player, so an
|
|
297
|
+
// unmuted session must not leave a neighbouring frame able to
|
|
298
|
+
// emit sound.
|
|
299
|
+
muted={muted || index !== current}
|
|
301
300
|
repeat
|
|
302
301
|
playInBackground={false}
|
|
303
302
|
playWhenInactive={false}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { StyleSheet } from 'react-native';
|
|
2
2
|
import { useTheme } from 'react-native-paper';
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
3
4
|
import type { MyMD3Theme } from '../../../core/providers/AmityUIKitProvider';
|
|
4
5
|
|
|
5
6
|
export const useStyles = () => {
|
|
6
7
|
const theme = useTheme<MyMD3Theme>();
|
|
8
|
+
const { top } = useSafeAreaInsets();
|
|
7
9
|
|
|
8
10
|
const styles = StyleSheet.create({
|
|
9
11
|
container: {
|
|
@@ -15,7 +17,13 @@ export const useStyles = () => {
|
|
|
15
17
|
alignItems: 'center',
|
|
16
18
|
justifyContent: 'space-between',
|
|
17
19
|
paddingHorizontal: 16,
|
|
18
|
-
|
|
20
|
+
// The viewer is a full-screen Modal, so its own content starts at y=0
|
|
21
|
+
// with nothing above it — the 56pt header sat under the notch / Dynamic
|
|
22
|
+
// Island and the close button could not be tapped at all. Inset the
|
|
23
|
+
// header instead of the container so the media itself still fills the
|
|
24
|
+
// screen behind it.
|
|
25
|
+
paddingTop: top,
|
|
26
|
+
height: 56 + top,
|
|
19
27
|
zIndex: 2,
|
|
20
28
|
},
|
|
21
29
|
headerBtn: {
|
|
@@ -25,6 +25,7 @@ import RenderTextWithMention from '../RenderTextWithMention/RenderTextWithMentio
|
|
|
25
25
|
import { IMentionPosition } from '../../../core/types';
|
|
26
26
|
import PollContent from '../PollContent';
|
|
27
27
|
import { MediaViewer, type MediaViewerItem } from '../MediaViewer';
|
|
28
|
+
import { classifyPostChildren } from '../../utils/postChildren';
|
|
28
29
|
|
|
29
30
|
interface IPostContent {
|
|
30
31
|
post: Amity.Post;
|
|
@@ -107,41 +108,8 @@ const PostContent: React.FC<IPostContent> = ({
|
|
|
107
108
|
})
|
|
108
109
|
);
|
|
109
110
|
|
|
110
|
-
const images
|
|
111
|
-
|
|
112
|
-
const polls: { pollId: string }[] = [];
|
|
113
|
-
const livestreams: Amity.Room['roomId'][] = [];
|
|
114
|
-
const media: Amity.Post[] = [];
|
|
115
|
-
|
|
116
|
-
response.forEach((item) => {
|
|
117
|
-
if (item?.dataType === 'image' && item?.data?.fileId) {
|
|
118
|
-
const url: string = `https://api.${apiRegion}.amity.co/api/v3/files/${item?.data.fileId}/download?size=medium`;
|
|
119
|
-
if (!images.includes(url)) {
|
|
120
|
-
images.push(url);
|
|
121
|
-
if (item.post) media.push(item.post);
|
|
122
|
-
}
|
|
123
|
-
} else if (
|
|
124
|
-
item?.dataType === 'video' &&
|
|
125
|
-
item?.data?.videoFileId.original
|
|
126
|
-
) {
|
|
127
|
-
const isExisted = videos.some(
|
|
128
|
-
(video) =>
|
|
129
|
-
video.videoFileId.original === item.data.videoFileId.original
|
|
130
|
-
);
|
|
131
|
-
if (!isExisted) {
|
|
132
|
-
videos.push(item.data);
|
|
133
|
-
if (item.post) media.push(item.post);
|
|
134
|
-
}
|
|
135
|
-
} else if (item?.dataType === 'poll') {
|
|
136
|
-
if (!polls.some((poll) => poll.pollId === item.data.pollId)) {
|
|
137
|
-
polls.push(item.data);
|
|
138
|
-
}
|
|
139
|
-
} else if (item?.dataType === 'room') {
|
|
140
|
-
if (!livestreams.includes(item.data.roomId)) {
|
|
141
|
-
livestreams.push(item.data.roomId);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
});
|
|
111
|
+
const { images, videos, polls, livestreams, media } =
|
|
112
|
+
classifyPostChildren(response, apiRegion);
|
|
145
113
|
|
|
146
114
|
// Set unconditionally so navigating to a post without media clears the
|
|
147
115
|
// previous media (see the effect below which no longer eagerly resets).
|