@amityco/react-native-social-uikit 4.1.0 → 4.1.1-11cdc854.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/core/assets/icons/xml.js +55 -1
- package/lib/commonjs/core/assets/icons/xml.js.map +1 -1
- package/lib/commonjs/core/hooks/usePostByIds.js +38 -0
- package/lib/commonjs/core/hooks/usePostByIds.js.map +1 -0
- package/lib/commonjs/social/components/LoadingImage/index.js +4 -3
- package/lib/commonjs/social/components/LoadingImage/index.js.map +1 -1
- package/lib/commonjs/social/components/LoadingImage/styles.js +4 -0
- package/lib/commonjs/social/components/LoadingImage/styles.js.map +1 -1
- package/lib/commonjs/social/components/LoadingVideo/index.js +12 -6
- package/lib/commonjs/social/components/LoadingVideo/index.js.map +1 -1
- package/lib/commonjs/social/components/LoadingVideo/styles.js +4 -0
- package/lib/commonjs/social/components/LoadingVideo/styles.js.map +1 -1
- package/lib/commonjs/social/components/MediaViewer/MediaViewer.js +266 -0
- package/lib/commonjs/social/components/MediaViewer/MediaViewer.js.map +1 -0
- package/lib/commonjs/social/components/MediaViewer/index.js +13 -0
- package/lib/commonjs/social/components/MediaViewer/index.js.map +1 -0
- package/lib/commonjs/social/components/MediaViewer/styles.js +116 -0
- package/lib/commonjs/social/components/MediaViewer/styles.js.map +1 -0
- package/lib/commonjs/social/components/PostContent/index.js +75 -120
- package/lib/commonjs/social/components/PostContent/index.js.map +1 -1
- package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js +180 -0
- package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -0
- package/lib/commonjs/social/components/SelectedMediaComponent/index.js +13 -0
- package/lib/commonjs/social/components/SelectedMediaComponent/index.js.map +1 -0
- package/lib/commonjs/social/components/SelectedMediaComponent/styles.js +43 -0
- package/lib/commonjs/social/components/SelectedMediaComponent/styles.js.map +1 -0
- package/lib/commonjs/social/constants/index.js +11 -0
- package/lib/commonjs/social/constants/index.js.map +1 -0
- package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js +280 -0
- package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js.map +1 -0
- package/lib/commonjs/social/elements/PostMediaElement/index.js +13 -0
- package/lib/commonjs/social/elements/PostMediaElement/index.js.map +1 -0
- package/lib/commonjs/social/elements/PostMediaElement/styles.js +117 -0
- package/lib/commonjs/social/elements/PostMediaElement/styles.js.map +1 -0
- package/lib/commonjs/social/features/post/Composer/PostComposer.js +91 -106
- package/lib/commonjs/social/features/post/Composer/PostComposer.js.map +1 -1
- package/lib/commonjs/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js +17 -7
- package/lib/commonjs/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js.map +1 -1
- package/lib/commonjs/social/features/post/components/MediaAttachment/MediaAttachment.js +17 -7
- package/lib/commonjs/social/features/post/components/MediaAttachment/MediaAttachment.js.map +1 -1
- package/lib/commonjs/social/utils/getFrameRatio.js +52 -0
- package/lib/commonjs/social/utils/getFrameRatio.js.map +1 -0
- package/lib/commonjs/social/utils/getIndicatorConfig.js +59 -0
- package/lib/commonjs/social/utils/getIndicatorConfig.js.map +1 -0
- package/lib/module/core/assets/icons/xml.js +48 -0
- package/lib/module/core/assets/icons/xml.js.map +1 -1
- package/lib/module/core/hooks/usePostByIds.js +32 -0
- package/lib/module/core/hooks/usePostByIds.js.map +1 -0
- package/lib/module/social/components/LoadingImage/index.js +4 -3
- package/lib/module/social/components/LoadingImage/index.js.map +1 -1
- package/lib/module/social/components/LoadingImage/styles.js +4 -0
- package/lib/module/social/components/LoadingImage/styles.js.map +1 -1
- package/lib/module/social/components/LoadingVideo/index.js +13 -7
- package/lib/module/social/components/LoadingVideo/index.js.map +1 -1
- package/lib/module/social/components/LoadingVideo/styles.js +4 -0
- package/lib/module/social/components/LoadingVideo/styles.js.map +1 -1
- package/lib/module/social/components/MediaViewer/MediaViewer.js +259 -0
- package/lib/module/social/components/MediaViewer/MediaViewer.js.map +1 -0
- package/lib/module/social/components/MediaViewer/index.js +2 -0
- package/lib/module/social/components/MediaViewer/index.js.map +1 -0
- package/lib/module/social/components/MediaViewer/styles.js +109 -0
- package/lib/module/social/components/MediaViewer/styles.js.map +1 -0
- package/lib/module/social/components/PostContent/index.js +77 -122
- package/lib/module/social/components/PostContent/index.js.map +1 -1
- package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js +174 -0
- package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -0
- package/lib/module/social/components/SelectedMediaComponent/index.js +2 -0
- package/lib/module/social/components/SelectedMediaComponent/index.js.map +1 -0
- package/lib/module/social/components/SelectedMediaComponent/styles.js +36 -0
- package/lib/module/social/components/SelectedMediaComponent/styles.js.map +1 -0
- package/lib/module/social/constants/index.js +5 -0
- package/lib/module/social/constants/index.js.map +1 -0
- package/lib/module/social/elements/PostMediaElement/PostMediaElement.js +273 -0
- package/lib/module/social/elements/PostMediaElement/PostMediaElement.js.map +1 -0
- package/lib/module/social/elements/PostMediaElement/index.js +2 -0
- package/lib/module/social/elements/PostMediaElement/index.js.map +1 -0
- package/lib/module/social/elements/PostMediaElement/styles.js +110 -0
- package/lib/module/social/elements/PostMediaElement/styles.js.map +1 -0
- package/lib/module/social/features/post/Composer/PostComposer.js +94 -109
- package/lib/module/social/features/post/Composer/PostComposer.js.map +1 -1
- package/lib/module/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js +17 -7
- package/lib/module/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js.map +1 -1
- package/lib/module/social/features/post/components/MediaAttachment/MediaAttachment.js +17 -7
- package/lib/module/social/features/post/components/MediaAttachment/MediaAttachment.js.map +1 -1
- package/lib/module/social/utils/getFrameRatio.js +44 -0
- package/lib/module/social/utils/getFrameRatio.js.map +1 -0
- package/lib/module/social/utils/getIndicatorConfig.js +53 -0
- package/lib/module/social/utils/getIndicatorConfig.js.map +1 -0
- package/lib/typescript/core/assets/icons/xml.d.ts +7 -0
- package/lib/typescript/core/assets/icons/xml.d.ts.map +1 -1
- package/lib/typescript/core/hooks/usePostByIds.d.ts +8 -0
- package/lib/typescript/core/hooks/usePostByIds.d.ts.map +1 -0
- package/lib/typescript/core/types/mention.d.ts +6 -0
- package/lib/typescript/core/types/mention.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingImage/index.d.ts +2 -1
- package/lib/typescript/social/components/LoadingImage/index.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingImage/styles.d.ts +4 -0
- package/lib/typescript/social/components/LoadingImage/styles.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingVideo/index.d.ts +2 -1
- package/lib/typescript/social/components/LoadingVideo/index.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingVideo/styles.d.ts +4 -0
- package/lib/typescript/social/components/LoadingVideo/styles.d.ts.map +1 -1
- package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts +17 -0
- package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts.map +1 -0
- package/lib/typescript/social/components/MediaViewer/index.d.ts +3 -0
- package/lib/typescript/social/components/MediaViewer/index.d.ts.map +1 -0
- package/lib/typescript/social/components/MediaViewer/styles.d.ts +99 -0
- package/lib/typescript/social/components/MediaViewer/styles.d.ts.map +1 -0
- package/lib/typescript/social/components/PostContent/index.d.ts.map +1 -1
- package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts +12 -0
- package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts.map +1 -0
- package/lib/typescript/social/components/SelectedMediaComponent/index.d.ts +3 -0
- package/lib/typescript/social/components/SelectedMediaComponent/index.d.ts.map +1 -0
- package/lib/typescript/social/components/SelectedMediaComponent/styles.d.ts +25 -0
- package/lib/typescript/social/components/SelectedMediaComponent/styles.d.ts.map +1 -0
- package/lib/typescript/social/constants/index.d.ts +4 -0
- package/lib/typescript/social/constants/index.d.ts.map +1 -0
- package/lib/typescript/social/elements/PostMediaElement/PostMediaElement.d.ts +15 -0
- package/lib/typescript/social/elements/PostMediaElement/PostMediaElement.d.ts.map +1 -0
- package/lib/typescript/social/elements/PostMediaElement/index.d.ts +3 -0
- package/lib/typescript/social/elements/PostMediaElement/index.d.ts.map +1 -0
- package/lib/typescript/social/elements/PostMediaElement/styles.d.ts +88 -0
- package/lib/typescript/social/elements/PostMediaElement/styles.d.ts.map +1 -0
- package/lib/typescript/social/features/post/Composer/PostComposer.d.ts.map +1 -1
- package/lib/typescript/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.d.ts +3 -0
- package/lib/typescript/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.d.ts.map +1 -1
- package/lib/typescript/social/features/post/components/MediaAttachment/MediaAttachment.d.ts +3 -0
- package/lib/typescript/social/features/post/components/MediaAttachment/MediaAttachment.d.ts.map +1 -1
- package/lib/typescript/social/utils/getFrameRatio.d.ts +21 -0
- package/lib/typescript/social/utils/getFrameRatio.d.ts.map +1 -0
- package/lib/typescript/social/utils/getIndicatorConfig.d.ts +11 -0
- package/lib/typescript/social/utils/getIndicatorConfig.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/core/assets/icons/xml.ts +60 -0
- package/src/core/hooks/usePostByIds.ts +33 -0
- package/src/core/types/mention.ts +6 -0
- package/src/social/components/LoadingImage/index.tsx +12 -2
- package/src/social/components/LoadingImage/styles.ts +4 -0
- package/src/social/components/LoadingVideo/index.tsx +28 -4
- package/src/social/components/LoadingVideo/styles.ts +4 -0
- package/src/social/components/MediaViewer/MediaViewer.tsx +384 -0
- package/src/social/components/MediaViewer/index.tsx +2 -0
- package/src/social/components/MediaViewer/styles.ts +109 -0
- package/src/social/components/PostContent/index.tsx +86 -159
- package/src/social/components/SelectedMediaComponent/SelectedMediaComponent.tsx +236 -0
- package/src/social/components/SelectedMediaComponent/index.ts +2 -0
- package/src/social/components/SelectedMediaComponent/styles.ts +38 -0
- package/src/social/constants/index.ts +4 -0
- package/src/social/elements/PostMediaElement/PostMediaElement.tsx +362 -0
- package/src/social/elements/PostMediaElement/index.tsx +5 -0
- package/src/social/elements/PostMediaElement/styles.ts +99 -0
- package/src/social/features/post/Composer/PostComposer.tsx +107 -123
- package/src/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx +10 -3
- package/src/social/features/post/components/MediaAttachment/MediaAttachment.tsx +14 -3
- package/src/social/utils/getFrameRatio.ts +42 -0
- package/src/social/utils/getIndicatorConfig.ts +62 -0
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
import { Fragment, memo, useCallback, useEffect, useState } from 'react';
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from 'react
|
|
10
|
-
import {
|
|
2
|
+
Fragment,
|
|
3
|
+
memo,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { Pressable, Text, TouchableOpacity, View } from 'react-native';
|
|
11
10
|
import { useStyles } from './styles';
|
|
12
11
|
import useAuth from '../../../core/hooks/useAuth';
|
|
13
12
|
import { IVideoPost, MediaUri } from '../legacy/Social/PostList';
|
|
14
13
|
import { getPostById } from '../../../core/legacy/feed';
|
|
15
14
|
import ImageView from '../legacy/react-native-image-viewing/dist';
|
|
15
|
+
import { SvgXml } from 'react-native-svg';
|
|
16
|
+
import { clearIcon } from '../../../core/assets/icons/xml';
|
|
16
17
|
import { RootState, useUIKitSelector } from '../../../core/stores/store';
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
PostMediaElement,
|
|
20
|
+
type PostMediaControls,
|
|
21
|
+
} from '../../elements/PostMediaElement';
|
|
18
22
|
import LivestreamContent from '../LivestreamContent';
|
|
19
23
|
import { LinkPreview } from '../PreviewLink';
|
|
20
24
|
import RenderTextWithMention from '../RenderTextWithMention/RenderTextWithMention';
|
|
21
25
|
import { IMentionPosition } from '../../../core/types';
|
|
22
26
|
import PollContent from '../PollContent';
|
|
27
|
+
import { MediaViewer, type MediaViewerItem } from '../MediaViewer';
|
|
23
28
|
|
|
24
29
|
interface IPostContent {
|
|
25
30
|
post: Amity.Post;
|
|
@@ -49,10 +54,16 @@ const PostContent: React.FC<IPostContent> = ({
|
|
|
49
54
|
const [videoPostsFullSize, setVideoPostsFullSize] = useState<MediaUri[]>([]);
|
|
50
55
|
const [visibleFullImage, setIsVisibleFullImage] = useState<boolean>(false);
|
|
51
56
|
const [imageIndex, setImageIndex] = useState<number>(0);
|
|
57
|
+
const [videoViewerIndex, setVideoViewerIndex] = useState<number | null>(null);
|
|
58
|
+
const [mediaLoading, setMediaLoading] = useState<boolean>(
|
|
59
|
+
(childrenPosts?.length ?? 0) > 0
|
|
60
|
+
);
|
|
61
|
+
const [mediaChildPosts, setMediaChildPosts] = useState<Amity.Post[]>([]);
|
|
62
|
+
|
|
63
|
+
const mediaControlsRef = useRef<PostMediaControls | null>(null);
|
|
64
|
+
const viewerIndexRef = useRef<number>(0);
|
|
52
65
|
|
|
53
66
|
const styles = useStyles();
|
|
54
|
-
let imageStyle: any = styles.imageLargePost;
|
|
55
|
-
let colStyle: any = styles.col2;
|
|
56
67
|
const { currentPostdetail } = useUIKitSelector(
|
|
57
68
|
(state: RootState) => state.postDetail
|
|
58
69
|
);
|
|
@@ -83,11 +94,16 @@ const PostContent: React.FC<IPostContent> = ({
|
|
|
83
94
|
}, [imagePosts, videoPosts, apiRegion]);
|
|
84
95
|
|
|
85
96
|
const getPostInfo = useCallback(async () => {
|
|
97
|
+
setMediaLoading((childrenPosts?.length ?? 0) > 0);
|
|
86
98
|
try {
|
|
87
99
|
const response = await Promise.all(
|
|
88
100
|
childrenPosts.map(async (id) => {
|
|
89
101
|
const { data: childrenPost } = await getPostById(id);
|
|
90
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
post: childrenPost,
|
|
104
|
+
dataType: childrenPost?.dataType,
|
|
105
|
+
data: childrenPost?.data,
|
|
106
|
+
};
|
|
91
107
|
})
|
|
92
108
|
);
|
|
93
109
|
|
|
@@ -95,12 +111,14 @@ const PostContent: React.FC<IPostContent> = ({
|
|
|
95
111
|
const videos: IVideoPost[] = [];
|
|
96
112
|
const polls: { pollId: string }[] = [];
|
|
97
113
|
const livestreams: Amity.Room['roomId'][] = [];
|
|
114
|
+
const media: Amity.Post[] = [];
|
|
98
115
|
|
|
99
116
|
response.forEach((item) => {
|
|
100
117
|
if (item?.dataType === 'image' && item?.data?.fileId) {
|
|
101
118
|
const url: string = `https://api.${apiRegion}.amity.co/api/v3/files/${item?.data.fileId}/download?size=medium`;
|
|
102
119
|
if (!images.includes(url)) {
|
|
103
120
|
images.push(url);
|
|
121
|
+
if (item.post) media.push(item.post);
|
|
104
122
|
}
|
|
105
123
|
} else if (
|
|
106
124
|
item?.dataType === 'video' &&
|
|
@@ -112,6 +130,7 @@ const PostContent: React.FC<IPostContent> = ({
|
|
|
112
130
|
);
|
|
113
131
|
if (!isExisted) {
|
|
114
132
|
videos.push(item.data);
|
|
133
|
+
if (item.post) media.push(item.post);
|
|
115
134
|
}
|
|
116
135
|
} else if (item?.dataType === 'poll') {
|
|
117
136
|
if (!polls.some((poll) => poll.pollId === item.data.pollId)) {
|
|
@@ -124,12 +143,11 @@ const PostContent: React.FC<IPostContent> = ({
|
|
|
124
143
|
}
|
|
125
144
|
});
|
|
126
145
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
146
|
+
// Set unconditionally so navigating to a post without media clears the
|
|
147
|
+
// previous media (see the effect below which no longer eagerly resets).
|
|
148
|
+
setMediaChildPosts(media);
|
|
149
|
+
setImagePosts(images);
|
|
150
|
+
setVideoPosts(videos);
|
|
133
151
|
if (polls.length > 0) {
|
|
134
152
|
setPollIds(polls);
|
|
135
153
|
}
|
|
@@ -138,168 +156,62 @@ const PostContent: React.FC<IPostContent> = ({
|
|
|
138
156
|
}
|
|
139
157
|
} catch (error) {
|
|
140
158
|
console.log('error: ', error);
|
|
159
|
+
} finally {
|
|
160
|
+
setMediaLoading(false);
|
|
141
161
|
}
|
|
142
162
|
}, [apiRegion, childrenPosts]);
|
|
143
163
|
|
|
144
164
|
useEffect(() => {
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
// Don't clear media here: getPostInfo resolves images/videos/media to their
|
|
166
|
+
// fresh values, so the existing media stays on screen until the new data
|
|
167
|
+
// arrives. Eagerly resetting caused a blank flash whenever the feed
|
|
168
|
+
// re-rendered (e.g. realtime updates), then the content popped back in.
|
|
147
169
|
getPostInfo();
|
|
148
170
|
}, [childrenPosts, currentPostdetail, postList, postListGlobal, getPostInfo]);
|
|
149
171
|
|
|
150
172
|
function onClickImage(index: number): void {
|
|
173
|
+
viewerIndexRef.current = index;
|
|
151
174
|
setIsVisibleFullImage(true);
|
|
152
175
|
setImageIndex(index);
|
|
153
176
|
}
|
|
154
177
|
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return `https://api.${apiRegion}.amity.co/api/v3/files/${item?.thumbnailFileId}/download?size=medium`;
|
|
160
|
-
})
|
|
161
|
-
: [];
|
|
162
|
-
const mediaPosts =
|
|
163
|
-
[...imagePosts].length > 0 ? [...imagePosts] : [...thumbnailFileIds];
|
|
164
|
-
const imageElement = mediaPosts.map((item: string, index: number) => {
|
|
165
|
-
if (mediaPosts.length === 1) {
|
|
166
|
-
imageStyle = styles.imageLargePost;
|
|
167
|
-
colStyle = styles.col6;
|
|
168
|
-
} else if (mediaPosts.length === 2) {
|
|
169
|
-
colStyle = styles.col3;
|
|
170
|
-
if (index === 0) {
|
|
171
|
-
imageStyle = [styles.imageLargePost, styles.imageMarginRight];
|
|
172
|
-
} else {
|
|
173
|
-
imageStyle = [styles.imageLargePost, styles.imageMarginLeft];
|
|
174
|
-
}
|
|
175
|
-
} else if (mediaPosts.length === 3) {
|
|
176
|
-
switch (index) {
|
|
177
|
-
case 0:
|
|
178
|
-
colStyle = styles.col6;
|
|
179
|
-
imageStyle = [styles.imageMediumPost, styles.imageMarginBottom];
|
|
180
|
-
break;
|
|
181
|
-
case 1:
|
|
182
|
-
colStyle = styles.col3;
|
|
183
|
-
imageStyle = [
|
|
184
|
-
styles.imageMediumPost,
|
|
185
|
-
styles.imageMarginTop,
|
|
186
|
-
styles.imageMarginRight,
|
|
187
|
-
];
|
|
188
|
-
break;
|
|
189
|
-
case 2:
|
|
190
|
-
colStyle = styles.col3;
|
|
191
|
-
imageStyle = [
|
|
192
|
-
styles.imageMediumPost,
|
|
193
|
-
styles.imageMarginTop,
|
|
194
|
-
styles.imageMarginLeft,
|
|
195
|
-
];
|
|
196
|
-
break;
|
|
197
|
-
|
|
198
|
-
default:
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
} else {
|
|
202
|
-
switch (index) {
|
|
203
|
-
case 0:
|
|
204
|
-
colStyle = styles.col6;
|
|
205
|
-
imageStyle = [
|
|
206
|
-
styles.imageMediumLargePost,
|
|
207
|
-
styles.imageMarginBottom,
|
|
208
|
-
];
|
|
209
|
-
break;
|
|
210
|
-
case 1:
|
|
211
|
-
colStyle = styles.col2;
|
|
212
|
-
imageStyle = [
|
|
213
|
-
styles.imageSmallPost,
|
|
214
|
-
styles.imageMarginTop,
|
|
215
|
-
styles.imageMarginRight,
|
|
216
|
-
];
|
|
217
|
-
break;
|
|
218
|
-
case 2:
|
|
219
|
-
colStyle = styles.col2;
|
|
220
|
-
imageStyle = [
|
|
221
|
-
styles.imageSmallPost,
|
|
222
|
-
styles.imageMarginTop,
|
|
223
|
-
styles.imageMarginLeft,
|
|
224
|
-
styles.imageMarginRight,
|
|
225
|
-
];
|
|
226
|
-
break;
|
|
227
|
-
case 3:
|
|
228
|
-
colStyle = styles.col2;
|
|
229
|
-
imageStyle = [
|
|
230
|
-
styles.imageSmallPost,
|
|
231
|
-
styles.imageMarginTop,
|
|
232
|
-
styles.imageMarginLeft,
|
|
233
|
-
];
|
|
234
|
-
break;
|
|
235
|
-
default:
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
178
|
+
function closeFullScreen(): void {
|
|
179
|
+
setIsVisibleFullImage(false);
|
|
180
|
+
mediaControlsRef.current?.slideTo(viewerIndexRef.current);
|
|
181
|
+
}
|
|
239
182
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
<Image
|
|
246
|
-
style={imageStyle}
|
|
247
|
-
source={{
|
|
248
|
-
uri: item,
|
|
249
|
-
}}
|
|
250
|
-
/>
|
|
251
|
-
{index === 3 && imagePosts.length > 4 && (
|
|
252
|
-
<View style={styles.overlay}>
|
|
253
|
-
<Text style={styles.overlayText}>{`+ ${
|
|
254
|
-
imagePosts.length - 3
|
|
255
|
-
}`}</Text>
|
|
256
|
-
</View>
|
|
257
|
-
)}
|
|
258
|
-
</View>
|
|
259
|
-
</TouchableWithoutFeedback>
|
|
260
|
-
</View>
|
|
261
|
-
);
|
|
262
|
-
});
|
|
263
|
-
if (imageElement.length < 3) {
|
|
264
|
-
return (
|
|
265
|
-
<View style={styles.imagesWrap}>
|
|
266
|
-
<View style={styles.row}>{imageElement}</View>
|
|
267
|
-
</View>
|
|
268
|
-
);
|
|
269
|
-
} else if (imageElement.length === 3) {
|
|
270
|
-
return (
|
|
271
|
-
<View style={[styles.imagesWrap]}>
|
|
272
|
-
<View style={styles.row}>{imageElement.slice(0, 1)}</View>
|
|
273
|
-
<View style={styles.row}>{imageElement.slice(1, 3)}</View>
|
|
274
|
-
</View>
|
|
275
|
-
);
|
|
276
|
-
} else {
|
|
277
|
-
return (
|
|
278
|
-
<View style={styles.imagesWrap}>
|
|
279
|
-
<View style={styles.row}>{imageElement.slice(0, 1)}</View>
|
|
280
|
-
<View style={styles.row}>{imageElement.slice(1, 4)}</View>
|
|
281
|
-
</View>
|
|
282
|
-
);
|
|
283
|
-
}
|
|
183
|
+
// UC2 (PDT-4309): video carousel opens the swipeable full-screen player
|
|
184
|
+
// (autoplay + mute-carry) instead of the single-video route.
|
|
185
|
+
function openVideoViewer(index: number): void {
|
|
186
|
+
viewerIndexRef.current = index;
|
|
187
|
+
setVideoViewerIndex(index);
|
|
284
188
|
}
|
|
285
189
|
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
</View>
|
|
291
|
-
);
|
|
190
|
+
function closeVideoViewer(lastIndex: number): void {
|
|
191
|
+
setVideoViewerIndex(null);
|
|
192
|
+
// Published post: return to the last-viewed frame.
|
|
193
|
+
mediaControlsRef.current?.slideTo(lastIndex);
|
|
292
194
|
}
|
|
293
195
|
|
|
196
|
+
const videoViewerItems: MediaViewerItem[] = videoPosts.map(
|
|
197
|
+
(item: IVideoPost) => ({
|
|
198
|
+
type: 'video',
|
|
199
|
+
// The /files/{original}/download endpoint returns the original video
|
|
200
|
+
// file (mp4), not an HLS playlist — no m3u8 type hint (let the player
|
|
201
|
+
// detect the container).
|
|
202
|
+
uri: `https://api.${apiRegion}.amity.co/api/v3/files/${item?.videoFileId?.original}/download`,
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
|
|
294
206
|
function renderImageHeader({ imageIndex: imgIndex }) {
|
|
295
207
|
return (
|
|
296
208
|
<View style={styles.headerContainer}>
|
|
297
209
|
<View style={styles.flexWidth}>
|
|
298
210
|
<TouchableOpacity
|
|
299
211
|
style={styles.closebtnIcon}
|
|
300
|
-
onPress={
|
|
212
|
+
onPress={closeFullScreen}
|
|
301
213
|
>
|
|
302
|
-
<
|
|
214
|
+
<SvgXml xml={clearIcon('white')} width={28} height={28} />
|
|
303
215
|
</TouchableOpacity>
|
|
304
216
|
</View>
|
|
305
217
|
<View style={styles.flexWidth}>
|
|
@@ -345,7 +257,13 @@ const PostContent: React.FC<IPostContent> = ({
|
|
|
345
257
|
roomId={livestreamId[0]}
|
|
346
258
|
/>
|
|
347
259
|
) : (
|
|
348
|
-
|
|
260
|
+
<PostMediaElement
|
|
261
|
+
posts={mediaChildPosts}
|
|
262
|
+
onImageClick={onClickImage}
|
|
263
|
+
onVideoClick={openVideoViewer}
|
|
264
|
+
controlsRef={mediaControlsRef}
|
|
265
|
+
loading={mediaLoading}
|
|
266
|
+
/>
|
|
349
267
|
)}
|
|
350
268
|
<ImageView
|
|
351
269
|
images={
|
|
@@ -355,11 +273,20 @@ const PostContent: React.FC<IPostContent> = ({
|
|
|
355
273
|
}
|
|
356
274
|
imageIndex={imageIndex}
|
|
357
275
|
visible={visibleFullImage}
|
|
358
|
-
onRequestClose={
|
|
276
|
+
onRequestClose={closeFullScreen}
|
|
277
|
+
onImageIndexChange={(idx: number) => {
|
|
278
|
+
viewerIndexRef.current = idx;
|
|
279
|
+
}}
|
|
359
280
|
isVideoButton={videoPosts.length > 0 ? true : false}
|
|
360
281
|
videoPosts={videoPosts}
|
|
361
282
|
HeaderComponent={renderImageHeader}
|
|
362
283
|
/>
|
|
284
|
+
<MediaViewer
|
|
285
|
+
visible={videoViewerIndex !== null}
|
|
286
|
+
items={videoViewerItems}
|
|
287
|
+
initialIndex={videoViewerIndex ?? 0}
|
|
288
|
+
onClose={closeVideoViewer}
|
|
289
|
+
/>
|
|
363
290
|
</Fragment>
|
|
364
291
|
);
|
|
365
292
|
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { Fragment, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Image,
|
|
4
|
+
LayoutChangeEvent,
|
|
5
|
+
Pressable,
|
|
6
|
+
ScrollView,
|
|
7
|
+
View,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import LoadingImage from '../LoadingImage';
|
|
10
|
+
import LoadingVideo from '../LoadingVideo';
|
|
11
|
+
import { MediaViewer, type MediaViewerItem } from '../MediaViewer';
|
|
12
|
+
import { IDisplayImage } from '../../../core/types';
|
|
13
|
+
import {
|
|
14
|
+
getFrameRatio,
|
|
15
|
+
getVideoDisplayDims,
|
|
16
|
+
FRAME_RATIO_VALUE,
|
|
17
|
+
DEFAULT_FRAME_RATIO,
|
|
18
|
+
type FrameRatio,
|
|
19
|
+
} from '../../utils/getFrameRatio';
|
|
20
|
+
import { useStyles } from './styles';
|
|
21
|
+
|
|
22
|
+
// Peek scroll: next slide peeks by (PEEK_INSET − SPACE_BETWEEN). The composer
|
|
23
|
+
// body (PostComposer `inputWrapper`) already insets everything 12px
|
|
24
|
+
// horizontally, so this component adds NO side padding of its own — it only
|
|
25
|
+
// sizes the slide so the peek lands at the spec's 31px (SelectedMediaComponent
|
|
26
|
+
// REQ-003a). slideWidth = trackWidth − PEEK_INSET; peek = PEEK_INSET − 8 = 31.
|
|
27
|
+
const PEEK_INSET = 39;
|
|
28
|
+
const SPACE_BETWEEN = 8;
|
|
29
|
+
|
|
30
|
+
export type SelectedMediaComponentProps = {
|
|
31
|
+
mediaType: 'image' | 'video';
|
|
32
|
+
media: IDisplayImage[];
|
|
33
|
+
onClose?: (originalPath: string, fileId?: string, postId?: string) => void;
|
|
34
|
+
onLoadFinish?: (
|
|
35
|
+
fileId: string,
|
|
36
|
+
fileUrl: string,
|
|
37
|
+
fileName: string,
|
|
38
|
+
index: number,
|
|
39
|
+
originalPath: string,
|
|
40
|
+
thumbnail?: string
|
|
41
|
+
) => void;
|
|
42
|
+
onUploadError?: (hasError: boolean, source: string) => void;
|
|
43
|
+
isEditMode?: boolean;
|
|
44
|
+
setIsUploading?: (arg: boolean) => void;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function SelectedMediaComponent({
|
|
48
|
+
mediaType,
|
|
49
|
+
media,
|
|
50
|
+
onClose,
|
|
51
|
+
onLoadFinish,
|
|
52
|
+
onUploadError,
|
|
53
|
+
isEditMode = false,
|
|
54
|
+
setIsUploading,
|
|
55
|
+
}: SelectedMediaComponentProps) {
|
|
56
|
+
const styles = useStyles();
|
|
57
|
+
const scrollRef = useRef<ScrollView>(null);
|
|
58
|
+
const [trackWidth, setTrackWidth] = useState(0);
|
|
59
|
+
const [ratio, setRatio] = useState<FrameRatio>(DEFAULT_FRAME_RATIO);
|
|
60
|
+
// Index of the frame the full-screen viewer was opened from (null = closed).
|
|
61
|
+
const [viewerIndex, setViewerIndex] = useState<number | null>(null);
|
|
62
|
+
|
|
63
|
+
const total = media.length;
|
|
64
|
+
const isCarousel = total > 1;
|
|
65
|
+
const aspectRatio = FRAME_RATIO_VALUE[ratio];
|
|
66
|
+
const slideWidth = trackWidth > 0 ? trackWidth - PEEK_INSET : 0;
|
|
67
|
+
|
|
68
|
+
// Frames follow the aspect ratio classified from the item currently in first
|
|
69
|
+
// position (REQ-003d/003e); recalculated when the first item changes.
|
|
70
|
+
const first = media[0];
|
|
71
|
+
const firstUrl = first?.url;
|
|
72
|
+
const firstWidth = first?.width;
|
|
73
|
+
const firstHeight = first?.height;
|
|
74
|
+
const firstRotation = first?.rotation;
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!firstUrl) {
|
|
77
|
+
setRatio(DEFAULT_FRAME_RATIO);
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
// Video: classify from the record/extractor dims, applying rotation
|
|
81
|
+
// (REQ-003d1 — MUST NOT silently fall back to 1:1 for video). Shares the
|
|
82
|
+
// one rotation derivation with the published carousel (REQ-SDK-002b).
|
|
83
|
+
if (mediaType === 'video') {
|
|
84
|
+
if (firstWidth && firstHeight) {
|
|
85
|
+
const { width, height } = getVideoDisplayDims({
|
|
86
|
+
width: firstWidth,
|
|
87
|
+
height: firstHeight,
|
|
88
|
+
rotation: firstRotation,
|
|
89
|
+
});
|
|
90
|
+
setRatio(getFrameRatio(width, height));
|
|
91
|
+
} else {
|
|
92
|
+
// Log the fallback so a square frame is never silently ambiguous
|
|
93
|
+
// (REQ-003d2).
|
|
94
|
+
console.log(
|
|
95
|
+
'[SelectedMediaComponent] video has no dimensions; defaulting ratio'
|
|
96
|
+
);
|
|
97
|
+
setRatio(DEFAULT_FRAME_RATIO);
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
// Image: prefer carried dims, else measure the source.
|
|
102
|
+
if (firstWidth && firstHeight) {
|
|
103
|
+
setRatio(getFrameRatio(firstWidth, firstHeight));
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
let active = true;
|
|
107
|
+
Image.getSize(
|
|
108
|
+
firstUrl,
|
|
109
|
+
(width, height) => {
|
|
110
|
+
if (active) setRatio(getFrameRatio(width, height));
|
|
111
|
+
},
|
|
112
|
+
() => {
|
|
113
|
+
if (active) setRatio(DEFAULT_FRAME_RATIO);
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
return () => {
|
|
117
|
+
active = false;
|
|
118
|
+
};
|
|
119
|
+
}, [firstUrl, firstWidth, firstHeight, firstRotation, mediaType]);
|
|
120
|
+
|
|
121
|
+
const onLayout = (event: LayoutChangeEvent) => {
|
|
122
|
+
const width = event.nativeEvent.layout.width;
|
|
123
|
+
if (width && width !== trackWidth) setTrackWidth(width);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const viewerItems: MediaViewerItem[] = media.map((m) => ({
|
|
127
|
+
type: mediaType,
|
|
128
|
+
uri: m.url,
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
// PDT-4310/4312: on close, return to the frame originally tapped from — not
|
|
132
|
+
// the last frame viewed in full screen.
|
|
133
|
+
const handleCloseViewer = () => {
|
|
134
|
+
const tapped = viewerIndex ?? 0;
|
|
135
|
+
setViewerIndex(null);
|
|
136
|
+
if (isCarousel && slideWidth > 0) {
|
|
137
|
+
scrollRef.current?.scrollTo({
|
|
138
|
+
x: tapped * (slideWidth + SPACE_BETWEEN),
|
|
139
|
+
animated: false,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const renderFrame = (item: IDisplayImage, index: number) => (
|
|
145
|
+
<View style={[styles.selectedMedia__frame, { aspectRatio }]}>
|
|
146
|
+
{mediaType === 'video' ? (
|
|
147
|
+
<LoadingVideo
|
|
148
|
+
carousel
|
|
149
|
+
source={item.url}
|
|
150
|
+
onClose={onClose}
|
|
151
|
+
index={index}
|
|
152
|
+
onLoadFinish={onLoadFinish}
|
|
153
|
+
onUploadError={onUploadError}
|
|
154
|
+
isUploaded={item.isUploaded}
|
|
155
|
+
fileId={item.fileId}
|
|
156
|
+
thumbNail={item.thumbNail as string}
|
|
157
|
+
fileCount={total}
|
|
158
|
+
isEditMode={isEditMode}
|
|
159
|
+
postId={item.postId}
|
|
160
|
+
setIsUploading={setIsUploading}
|
|
161
|
+
onPlay={() => setViewerIndex(index)}
|
|
162
|
+
/>
|
|
163
|
+
) : (
|
|
164
|
+
<Pressable
|
|
165
|
+
style={styles.selectedMedia__mediaButton}
|
|
166
|
+
onPress={() => setViewerIndex(index)}
|
|
167
|
+
>
|
|
168
|
+
<LoadingImage
|
|
169
|
+
carousel
|
|
170
|
+
source={item.url}
|
|
171
|
+
onClose={onClose}
|
|
172
|
+
index={index}
|
|
173
|
+
onLoadFinish={onLoadFinish}
|
|
174
|
+
onUploadError={onUploadError}
|
|
175
|
+
isUploaded={item.isUploaded}
|
|
176
|
+
fileId={item.fileId}
|
|
177
|
+
fileCount={total}
|
|
178
|
+
isEditMode={isEditMode}
|
|
179
|
+
postId={item.postId}
|
|
180
|
+
setIsUploading={setIsUploading}
|
|
181
|
+
/>
|
|
182
|
+
</Pressable>
|
|
183
|
+
)}
|
|
184
|
+
</View>
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (total === 0) return null;
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<Fragment>
|
|
191
|
+
{!isCarousel ? (
|
|
192
|
+
<View style={styles.selectedMedia}>
|
|
193
|
+
<View style={styles.selectedMedia__track} onLayout={onLayout}>
|
|
194
|
+
{renderFrame(media[0], 0)}
|
|
195
|
+
</View>
|
|
196
|
+
</View>
|
|
197
|
+
) : (
|
|
198
|
+
<View style={styles.selectedMedia}>
|
|
199
|
+
<View style={styles.selectedMedia__track} onLayout={onLayout}>
|
|
200
|
+
{slideWidth > 0 && (
|
|
201
|
+
<ScrollView
|
|
202
|
+
ref={scrollRef}
|
|
203
|
+
horizontal
|
|
204
|
+
showsHorizontalScrollIndicator={false}
|
|
205
|
+
decelerationRate="fast"
|
|
206
|
+
snapToInterval={slideWidth + SPACE_BETWEEN}
|
|
207
|
+
snapToAlignment="start"
|
|
208
|
+
>
|
|
209
|
+
{media.map((item, index) => (
|
|
210
|
+
<View
|
|
211
|
+
key={`${item.url}-${index}`}
|
|
212
|
+
style={[
|
|
213
|
+
styles.selectedMedia__slide,
|
|
214
|
+
{
|
|
215
|
+
width: slideWidth,
|
|
216
|
+
marginRight: index < total - 1 ? SPACE_BETWEEN : 0,
|
|
217
|
+
},
|
|
218
|
+
]}
|
|
219
|
+
>
|
|
220
|
+
{renderFrame(item, index)}
|
|
221
|
+
</View>
|
|
222
|
+
))}
|
|
223
|
+
</ScrollView>
|
|
224
|
+
)}
|
|
225
|
+
</View>
|
|
226
|
+
</View>
|
|
227
|
+
)}
|
|
228
|
+
<MediaViewer
|
|
229
|
+
visible={viewerIndex !== null}
|
|
230
|
+
items={viewerItems}
|
|
231
|
+
initialIndex={viewerIndex ?? 0}
|
|
232
|
+
onClose={handleCloseViewer}
|
|
233
|
+
/>
|
|
234
|
+
</Fragment>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { useTheme } from 'react-native-paper';
|
|
3
|
+
import type { MyMD3Theme } from '../../../core/providers/AmityUIKitProvider';
|
|
4
|
+
|
|
5
|
+
// Values map 1:1 from web PR #1830 SelectedMediaComponent.module.css (1rem = 16dp).
|
|
6
|
+
// Web composer preview: peek scroll row (slidesPerView=auto, spaceBetween 8,
|
|
7
|
+
// slide width = calc(100% - 3.4375rem) = track - 55). NO counter, NO dots.
|
|
8
|
+
export const useStyles = () => {
|
|
9
|
+
const theme = useTheme<MyMD3Theme>();
|
|
10
|
+
|
|
11
|
+
return StyleSheet.create({
|
|
12
|
+
selectedMedia: {
|
|
13
|
+
width: '100%',
|
|
14
|
+
paddingVertical: 12, // web: padding 0.75rem 0
|
|
15
|
+
},
|
|
16
|
+
selectedMedia__track: {
|
|
17
|
+
position: 'relative',
|
|
18
|
+
width: '100%',
|
|
19
|
+
},
|
|
20
|
+
selectedMedia__slide: {
|
|
21
|
+
// width + marginRight (spaceBetween 8) set inline from measured track width
|
|
22
|
+
},
|
|
23
|
+
selectedMedia__frame: {
|
|
24
|
+
width: '100%',
|
|
25
|
+
overflow: 'hidden',
|
|
26
|
+
// web: no border-radius; background var(--asc-color-base-shade4)
|
|
27
|
+
backgroundColor: theme.colors.baseShade4,
|
|
28
|
+
},
|
|
29
|
+
selectedMedia__media: {
|
|
30
|
+
width: '100%',
|
|
31
|
+
height: '100%',
|
|
32
|
+
},
|
|
33
|
+
selectedMedia__mediaButton: {
|
|
34
|
+
width: '100%',
|
|
35
|
+
height: '100%',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
};
|