@amityco/react-native-social-uikit 4.1.0 → 4.1.1-21d3e899.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 +43 -1
- package/lib/commonjs/core/assets/icons/xml.js.map +1 -1
- 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/MediaSelector/MediaSelector.js +164 -0
- package/lib/commonjs/social/components/MediaSelector/MediaSelector.js.map +1 -0
- package/lib/commonjs/social/components/MediaSelector/index.js +13 -0
- package/lib/commonjs/social/components/MediaSelector/index.js.map +1 -0
- package/lib/commonjs/social/components/MediaSelector/styles.js +130 -0
- package/lib/commonjs/social/components/MediaSelector/styles.js.map +1 -0
- package/lib/commonjs/social/components/MediaViewer/MediaViewer.js +134 -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 +60 -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 +147 -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 +244 -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 +57 -91
- 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 +38 -0
- package/lib/module/core/assets/icons/xml.js.map +1 -1
- 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/MediaSelector/MediaSelector.js +158 -0
- package/lib/module/social/components/MediaSelector/MediaSelector.js.map +1 -0
- package/lib/module/social/components/MediaSelector/index.js +2 -0
- package/lib/module/social/components/MediaSelector/index.js.map +1 -0
- package/lib/module/social/components/MediaSelector/styles.js +123 -0
- package/lib/module/social/components/MediaSelector/styles.js.map +1 -0
- package/lib/module/social/components/MediaViewer/MediaViewer.js +127 -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 +53 -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 +141 -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 +237 -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 +59 -93
- 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 +5 -0
- package/lib/typescript/core/assets/icons/xml.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/MediaSelector/MediaSelector.d.ts +14 -0
- package/lib/typescript/social/components/MediaSelector/MediaSelector.d.ts.map +1 -0
- package/lib/typescript/social/components/MediaSelector/index.d.ts +2 -0
- package/lib/typescript/social/components/MediaSelector/index.d.ts.map +1 -0
- package/lib/typescript/social/components/MediaSelector/styles.d.ts +116 -0
- package/lib/typescript/social/components/MediaSelector/styles.d.ts.map +1 -0
- 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 +48 -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 +5 -3
- package/src/core/assets/icons/xml.ts +46 -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/MediaSelector/MediaSelector.tsx +231 -0
- package/src/social/components/MediaSelector/index.tsx +1 -0
- package/src/social/components/MediaSelector/styles.ts +124 -0
- package/src/social/components/MediaViewer/MediaViewer.tsx +168 -0
- package/src/social/components/MediaViewer/index.tsx +2 -0
- package/src/social/components/MediaViewer/styles.ts +53 -0
- package/src/social/components/PostContent/index.tsx +86 -159
- package/src/social/components/SelectedMediaComponent/SelectedMediaComponent.tsx +202 -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 +318 -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 +70 -96
- 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
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { useMemo, useRef, useState, type MutableRefObject } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Image,
|
|
4
|
+
ImageStyle,
|
|
5
|
+
LayoutChangeEvent,
|
|
6
|
+
NativeScrollEvent,
|
|
7
|
+
NativeSyntheticEvent,
|
|
8
|
+
Pressable,
|
|
9
|
+
ScrollView,
|
|
10
|
+
StyleProp,
|
|
11
|
+
useWindowDimensions,
|
|
12
|
+
View,
|
|
13
|
+
} from 'react-native';
|
|
14
|
+
import { SvgXml } from 'react-native-svg';
|
|
15
|
+
import { Typography } from '../../../core/components/Typography/Typography';
|
|
16
|
+
import useAuth from '../../../core/hooks/useAuth';
|
|
17
|
+
import {
|
|
18
|
+
videoControlIcon,
|
|
19
|
+
brokenImageIcon,
|
|
20
|
+
} from '../../../core/assets/icons/xml';
|
|
21
|
+
import {
|
|
22
|
+
getFrameRatio,
|
|
23
|
+
getVideoDisplayDims,
|
|
24
|
+
FRAME_RATIO_VALUE,
|
|
25
|
+
DEFAULT_FRAME_RATIO,
|
|
26
|
+
type FrameRatio,
|
|
27
|
+
} from '../../utils/getFrameRatio';
|
|
28
|
+
import { getIndicatorConfig } from '../../utils/getIndicatorConfig';
|
|
29
|
+
import { useStyles } from './styles';
|
|
30
|
+
|
|
31
|
+
type MediaPost = Amity.Post<'image'> | Amity.Post<'video'>;
|
|
32
|
+
|
|
33
|
+
function isMediaPost(post: Amity.Post): post is MediaPost {
|
|
34
|
+
return post.dataType === 'image' || post.dataType === 'video';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getAttachmentRatio(post?: MediaPost): FrameRatio {
|
|
38
|
+
if (!post) return DEFAULT_FRAME_RATIO;
|
|
39
|
+
|
|
40
|
+
if (post.dataType === 'image') {
|
|
41
|
+
const image = post.getImageInfo();
|
|
42
|
+
return getFrameRatio(image?.getWidth(), image?.getHeight());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const video = post.getVideoInfo();
|
|
46
|
+
const { width, height } = getVideoDisplayDims({
|
|
47
|
+
width: video?.getWidth(),
|
|
48
|
+
height: video?.getHeight(),
|
|
49
|
+
rotation: video?.getRotation(),
|
|
50
|
+
});
|
|
51
|
+
return getFrameRatio(width, height);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type PostMediaControls = {
|
|
55
|
+
slideTo: (index: number) => void;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type PostMediaElementProps = {
|
|
59
|
+
posts: Amity.Post[];
|
|
60
|
+
onImageClick: (index: number) => void;
|
|
61
|
+
onVideoClick: (index: number) => void;
|
|
62
|
+
controlsRef?: MutableRefObject<PostMediaControls | null>;
|
|
63
|
+
/** While the post's media children are still being fetched, show a
|
|
64
|
+
* placeholder frame instead of nothing (avoids a blank → pop). */
|
|
65
|
+
loading?: boolean;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export function PostMediaElement({
|
|
69
|
+
posts,
|
|
70
|
+
onImageClick,
|
|
71
|
+
onVideoClick,
|
|
72
|
+
controlsRef,
|
|
73
|
+
loading = false,
|
|
74
|
+
}: PostMediaElementProps) {
|
|
75
|
+
const styles = useStyles();
|
|
76
|
+
const { width: screenWidth } = useWindowDimensions();
|
|
77
|
+
const [current, setCurrent] = useState(0);
|
|
78
|
+
const [trackWidth, setTrackWidth] = useState(0);
|
|
79
|
+
const scrollRef = useRef<ScrollView>(null);
|
|
80
|
+
|
|
81
|
+
const mediaPosts = useMemo(() => (posts ?? []).filter(isMediaPost), [posts]);
|
|
82
|
+
const ratio = useMemo(() => getAttachmentRatio(mediaPosts[0]), [mediaPosts]);
|
|
83
|
+
|
|
84
|
+
if (mediaPosts.length === 0) {
|
|
85
|
+
if (!loading) return null;
|
|
86
|
+
// Loading placeholder: a full-bleed frame in the base-shade4 colour, so the
|
|
87
|
+
// media area doesn't flash blank before the child posts resolve.
|
|
88
|
+
return (
|
|
89
|
+
<View style={styles.postMedia}>
|
|
90
|
+
<View style={styles.postMedia__track}>
|
|
91
|
+
<View
|
|
92
|
+
style={[
|
|
93
|
+
styles.postMedia__frame,
|
|
94
|
+
{ aspectRatio: FRAME_RATIO_VALUE[DEFAULT_FRAME_RATIO] },
|
|
95
|
+
]}
|
|
96
|
+
/>
|
|
97
|
+
</View>
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const total = mediaPosts.length;
|
|
103
|
+
const isCarousel = total > 1;
|
|
104
|
+
const aspectRatio = FRAME_RATIO_VALUE[ratio];
|
|
105
|
+
// The carousel is full-bleed (screen width); use that immediately so the
|
|
106
|
+
// ScrollView renders without waiting for onLayout — otherwise there is an
|
|
107
|
+
// empty block between the placeholder and the first frame.
|
|
108
|
+
const slideWidth = trackWidth || screenWidth;
|
|
109
|
+
|
|
110
|
+
const handleFrameClick = (index: number) => {
|
|
111
|
+
const post = mediaPosts[index];
|
|
112
|
+
if (post.dataType === 'image') onImageClick(index);
|
|
113
|
+
else onVideoClick(index);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const onLayout = (event: LayoutChangeEvent) => {
|
|
117
|
+
const width = event.nativeEvent.layout.width;
|
|
118
|
+
if (width && width !== trackWidth) setTrackWidth(width);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const onMomentumScrollEnd = (
|
|
122
|
+
event: NativeSyntheticEvent<NativeScrollEvent>
|
|
123
|
+
) => {
|
|
124
|
+
if (!slideWidth) return;
|
|
125
|
+
const nextIndex = Math.round(
|
|
126
|
+
event.nativeEvent.contentOffset.x / slideWidth
|
|
127
|
+
);
|
|
128
|
+
if (nextIndex !== current) setCurrent(nextIndex);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (controlsRef) {
|
|
132
|
+
controlsRef.current = {
|
|
133
|
+
slideTo: (index: number) => {
|
|
134
|
+
scrollRef.current?.scrollTo({
|
|
135
|
+
x: index * slideWidth,
|
|
136
|
+
animated: false,
|
|
137
|
+
});
|
|
138
|
+
setCurrent(index);
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!isCarousel) {
|
|
144
|
+
return (
|
|
145
|
+
<View style={styles.postMedia}>
|
|
146
|
+
<View style={styles.postMedia__track} onLayout={onLayout}>
|
|
147
|
+
<MediaFrame
|
|
148
|
+
post={mediaPosts[0]}
|
|
149
|
+
aspectRatio={aspectRatio}
|
|
150
|
+
onPress={() => handleFrameClick(0)}
|
|
151
|
+
/>
|
|
152
|
+
</View>
|
|
153
|
+
</View>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<View style={styles.postMedia}>
|
|
159
|
+
<View style={styles.postMedia__track} onLayout={onLayout}>
|
|
160
|
+
<ScrollView
|
|
161
|
+
ref={scrollRef}
|
|
162
|
+
horizontal
|
|
163
|
+
pagingEnabled
|
|
164
|
+
showsHorizontalScrollIndicator={false}
|
|
165
|
+
onMomentumScrollEnd={onMomentumScrollEnd}
|
|
166
|
+
scrollEventThrottle={16}
|
|
167
|
+
decelerationRate="fast"
|
|
168
|
+
>
|
|
169
|
+
{mediaPosts.map((post, index) => (
|
|
170
|
+
<View
|
|
171
|
+
key={post.postId}
|
|
172
|
+
style={[styles.postMedia__slide, { width: slideWidth }]}
|
|
173
|
+
>
|
|
174
|
+
<MediaFrame
|
|
175
|
+
post={post}
|
|
176
|
+
aspectRatio={aspectRatio}
|
|
177
|
+
onPress={() => handleFrameClick(index)}
|
|
178
|
+
/>
|
|
179
|
+
</View>
|
|
180
|
+
))}
|
|
181
|
+
</ScrollView>
|
|
182
|
+
<PositionCounter current={current} total={total} />
|
|
183
|
+
</View>
|
|
184
|
+
<PaginationIndicator current={current} total={total} />
|
|
185
|
+
</View>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
type PaginationIndicatorProps = {
|
|
190
|
+
current: number;
|
|
191
|
+
total: number;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
function PaginationIndicator({ current, total }: PaginationIndicatorProps) {
|
|
195
|
+
const styles = useStyles();
|
|
196
|
+
const { dots } = getIndicatorConfig(current, total);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<View style={styles.postMedia__indicator}>
|
|
200
|
+
{dots.map((dot) => (
|
|
201
|
+
<View key={dot.key} style={styles.postMedia__dotSlot}>
|
|
202
|
+
<View
|
|
203
|
+
style={[
|
|
204
|
+
styles.postMedia__dot,
|
|
205
|
+
dot.state === 'active'
|
|
206
|
+
? styles.postMedia__dotActive
|
|
207
|
+
: dot.state === 'edge'
|
|
208
|
+
? styles.postMedia__dotEdge
|
|
209
|
+
: styles.postMedia__dotInactive,
|
|
210
|
+
]}
|
|
211
|
+
/>
|
|
212
|
+
</View>
|
|
213
|
+
))}
|
|
214
|
+
</View>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
type PositionCounterProps = {
|
|
219
|
+
current: number;
|
|
220
|
+
total: number;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
function PositionCounter({ current, total }: PositionCounterProps) {
|
|
224
|
+
const styles = useStyles();
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<View style={styles.postMedia__counter}>
|
|
228
|
+
<Typography.Body style={styles.postMedia__counterLabel}>
|
|
229
|
+
{current + 1}/{total}
|
|
230
|
+
</Typography.Body>
|
|
231
|
+
</View>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
type MediaFrameProps = {
|
|
236
|
+
post: MediaPost;
|
|
237
|
+
aspectRatio: number;
|
|
238
|
+
onPress: () => void;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
function MediaFrame({ post, aspectRatio, onPress }: MediaFrameProps) {
|
|
242
|
+
const styles = useStyles();
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<Pressable
|
|
246
|
+
onPress={onPress}
|
|
247
|
+
style={[styles.postMedia__frame, { aspectRatio }]}
|
|
248
|
+
>
|
|
249
|
+
{post.dataType === 'image' ? (
|
|
250
|
+
<ImageMedia post={post as Amity.Post<'image'>} />
|
|
251
|
+
) : (
|
|
252
|
+
<VideoMedia post={post as Amity.Post<'video'>} />
|
|
253
|
+
)}
|
|
254
|
+
</Pressable>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
type ImageMediaProps = { post: Amity.Post<'image'> };
|
|
259
|
+
|
|
260
|
+
function ImageMedia({ post }: ImageMediaProps) {
|
|
261
|
+
const styles = useStyles();
|
|
262
|
+
const { apiRegion } = useAuth();
|
|
263
|
+
const [isBroken, setIsBroken] = useState(false);
|
|
264
|
+
const fileId = post.getImageInfo()?.fileId;
|
|
265
|
+
const url = fileId
|
|
266
|
+
? `https://api.${apiRegion}.amity.co/api/v3/files/${fileId}/download?size=large`
|
|
267
|
+
: undefined;
|
|
268
|
+
|
|
269
|
+
if (!url || isBroken) return <BrokenFrame />;
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<Image
|
|
273
|
+
style={styles.postMedia__media as StyleProp<ImageStyle>}
|
|
274
|
+
source={{ uri: url }}
|
|
275
|
+
resizeMode="cover"
|
|
276
|
+
onError={() => setIsBroken(true)}
|
|
277
|
+
/>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
type VideoMediaProps = { post: Amity.Post<'video'> };
|
|
282
|
+
|
|
283
|
+
function VideoMedia({ post }: VideoMediaProps) {
|
|
284
|
+
const styles = useStyles();
|
|
285
|
+
const { apiRegion } = useAuth();
|
|
286
|
+
const [isBroken, setIsBroken] = useState(false);
|
|
287
|
+
|
|
288
|
+
const data = post.data as Amity.ContentDataVideo | undefined;
|
|
289
|
+
const thumbnailFileId = data?.thumbnailFileId;
|
|
290
|
+
const url = thumbnailFileId
|
|
291
|
+
? `https://api.${apiRegion}.amity.co/api/v3/files/${thumbnailFileId}/download?size=large`
|
|
292
|
+
: undefined;
|
|
293
|
+
|
|
294
|
+
if (!url || isBroken) return <BrokenFrame />;
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<>
|
|
298
|
+
<Image
|
|
299
|
+
style={styles.postMedia__media as StyleProp<ImageStyle>}
|
|
300
|
+
source={{ uri: url }}
|
|
301
|
+
resizeMode="cover"
|
|
302
|
+
onError={() => setIsBroken(true)}
|
|
303
|
+
/>
|
|
304
|
+
<View style={styles.postMedia__playButton}>
|
|
305
|
+
<SvgXml xml={videoControlIcon} width="40" height="40" />
|
|
306
|
+
</View>
|
|
307
|
+
</>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function BrokenFrame() {
|
|
312
|
+
const styles = useStyles();
|
|
313
|
+
return (
|
|
314
|
+
<View style={styles.postMedia__broken}>
|
|
315
|
+
<SvgXml xml={brokenImageIcon()} width={32} height={32} />
|
|
316
|
+
</View>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
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 PostMediaElement.module.css (1rem = 16dp).
|
|
6
|
+
// Colors resolve through theme tokens (web CSS variables), never hardcoded.
|
|
7
|
+
export const useStyles = () => {
|
|
8
|
+
const theme = useTheme<MyMD3Theme>();
|
|
9
|
+
|
|
10
|
+
const styles = StyleSheet.create({
|
|
11
|
+
postMedia: {
|
|
12
|
+
// Full-bleed: cancel the post card's 16px horizontal padding so the media
|
|
13
|
+
// spans the full card width (web PR #1830/#1834: width calc(100% + 2rem),
|
|
14
|
+
// margin-left/right -1rem).
|
|
15
|
+
marginHorizontal: -16,
|
|
16
|
+
paddingVertical: 8, // web: padding 0.5rem 0
|
|
17
|
+
},
|
|
18
|
+
postMedia__track: {
|
|
19
|
+
position: 'relative',
|
|
20
|
+
width: '100%',
|
|
21
|
+
},
|
|
22
|
+
postMedia__slide: {
|
|
23
|
+
// width is set dynamically to the measured track width so paging aligns
|
|
24
|
+
},
|
|
25
|
+
postMedia__frame: {
|
|
26
|
+
width: '100%',
|
|
27
|
+
overflow: 'hidden',
|
|
28
|
+
// web: no border-radius; background var(--asc-color-base-shade4)
|
|
29
|
+
backgroundColor: theme.colors.baseShade4,
|
|
30
|
+
},
|
|
31
|
+
postMedia__media: {
|
|
32
|
+
width: '100%',
|
|
33
|
+
height: '100%',
|
|
34
|
+
},
|
|
35
|
+
postMedia__playButton: {
|
|
36
|
+
position: 'absolute',
|
|
37
|
+
top: '50%',
|
|
38
|
+
left: '50%',
|
|
39
|
+
// web: 2.5rem (40dp), centred via translate(-50%, -50%)
|
|
40
|
+
width: 40,
|
|
41
|
+
height: 40,
|
|
42
|
+
transform: [{ translateX: -20 }, { translateY: -20 }],
|
|
43
|
+
zIndex: 1,
|
|
44
|
+
},
|
|
45
|
+
postMedia__broken: {
|
|
46
|
+
flex: 1,
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
justifyContent: 'center',
|
|
49
|
+
backgroundColor: theme.colors.baseShade4,
|
|
50
|
+
},
|
|
51
|
+
postMedia__counter: {
|
|
52
|
+
position: 'absolute',
|
|
53
|
+
top: 8, // web: 0.5rem
|
|
54
|
+
right: 8, // web: 0.5rem
|
|
55
|
+
alignItems: 'center',
|
|
56
|
+
justifyContent: 'center',
|
|
57
|
+
paddingVertical: 6, // web: 0.375rem
|
|
58
|
+
paddingHorizontal: 12, // web: 0.75rem
|
|
59
|
+
borderRadius: 999, // web: --asc-border-radius-full
|
|
60
|
+
backgroundColor: 'rgba(0,0,0,0.5)', // web: --asc-color-transparent-black (#00000080)
|
|
61
|
+
},
|
|
62
|
+
postMedia__counterLabel: {
|
|
63
|
+
color: '#FFFFFF', // web: var(--asc-color-white)
|
|
64
|
+
},
|
|
65
|
+
postMedia__indicator: {
|
|
66
|
+
flexDirection: 'row',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
justifyContent: 'center',
|
|
69
|
+
gap: 4, // web: gap 0.25rem
|
|
70
|
+
height: 6, // web: height 0.375rem
|
|
71
|
+
marginTop: 12, // web: parent .postMedia column gap 0.75rem
|
|
72
|
+
},
|
|
73
|
+
postMedia__dotSlot: {
|
|
74
|
+
alignItems: 'center',
|
|
75
|
+
justifyContent: 'center',
|
|
76
|
+
width: 6, // web: 0.375rem
|
|
77
|
+
height: 6, // web: 0.375rem
|
|
78
|
+
},
|
|
79
|
+
postMedia__dot: {
|
|
80
|
+
borderRadius: 999, // web: border-radius 50%
|
|
81
|
+
backgroundColor: theme.colors.baseShade4, // inactive/edge: base-shade4
|
|
82
|
+
},
|
|
83
|
+
postMedia__dotActive: {
|
|
84
|
+
width: 6,
|
|
85
|
+
height: 6,
|
|
86
|
+
backgroundColor: theme.colors.base, // web: var(--asc-color-base-default)
|
|
87
|
+
},
|
|
88
|
+
postMedia__dotInactive: {
|
|
89
|
+
width: 6,
|
|
90
|
+
height: 6,
|
|
91
|
+
},
|
|
92
|
+
postMedia__dotEdge: {
|
|
93
|
+
width: 3, // web: 0.1875rem
|
|
94
|
+
height: 3,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return styles;
|
|
99
|
+
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Alert,
|
|
3
|
-
FlatList,
|
|
4
3
|
Keyboard,
|
|
5
4
|
KeyboardAvoidingView,
|
|
6
5
|
Platform,
|
|
@@ -48,12 +47,11 @@ import AmityMediaAttachmentComponent from '../components/MediaAttachment';
|
|
|
48
47
|
import AmityDetailedMediaAttachmentComponent from '../components/DetailedMediaAttachment';
|
|
49
48
|
import { useKeyboardStatus } from '../../../hooks';
|
|
50
49
|
import ImagePicker, {
|
|
51
|
-
launchImageLibrary,
|
|
52
50
|
type Asset,
|
|
53
51
|
launchCamera,
|
|
54
52
|
} from 'react-native-image-picker';
|
|
55
|
-
import
|
|
56
|
-
import
|
|
53
|
+
import { SelectedMediaComponent } from '../../../components/SelectedMediaComponent';
|
|
54
|
+
import { MediaSelector } from '../../../components/MediaSelector';
|
|
57
55
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
58
56
|
import { RootStackParamList } from '../../../../core/routes/RouteParamList';
|
|
59
57
|
import {
|
|
@@ -117,6 +115,9 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
|
|
|
117
115
|
const [isSwipeup, setIsSwipeup] = useState(true);
|
|
118
116
|
const [deletedPostIds, setDeletedPostIds] = useState<string[]>([]);
|
|
119
117
|
const [isUploading, setIsUploading] = useState(false);
|
|
118
|
+
const [mediaSelectorType, setMediaSelectorType] = useState<
|
|
119
|
+
'photo' | 'video' | null
|
|
120
|
+
>(null);
|
|
120
121
|
const [hasChangedAttachment, setHasChangedAttachment] = useState(false);
|
|
121
122
|
const [imageErrors, setImageErrors] = useState<Set<string>>(new Set());
|
|
122
123
|
const [videoErrors, setVideoErrors] = useState<Set<string>>(new Set());
|
|
@@ -584,64 +585,42 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
|
|
|
584
585
|
}, [displayImages.length, displayVideos.length, pickCamera]);
|
|
585
586
|
|
|
586
587
|
const onPressImage = useCallback(async () => {
|
|
587
|
-
if (displayImages.length
|
|
588
|
+
if (displayImages.length >= 10)
|
|
588
589
|
return Alert.alert(
|
|
589
590
|
'Maximum upload limit reached',
|
|
590
591
|
"You've reached the upload limit of 10 images. Any additional images will not be saved."
|
|
591
592
|
);
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
quality: 1,
|
|
595
|
-
selectionLimit: 10 - displayImages.length,
|
|
596
|
-
});
|
|
597
|
-
if (!result.didCancel && result.assets && result.assets.length > 0) {
|
|
598
|
-
const imageUriArr: string[] = result.assets.map(
|
|
599
|
-
(item: Asset) => item.uri
|
|
600
|
-
) as string[];
|
|
601
|
-
const mediaOj = processMedia(imageUriArr);
|
|
602
|
-
setDisplayImages((prev) => {
|
|
603
|
-
const updatedArray = [...prev, ...mediaOj];
|
|
604
|
-
if (updatedArray.length > 10) {
|
|
605
|
-
Alert.alert(
|
|
606
|
-
'Maximum number of images exceeded',
|
|
607
|
-
'Maximum number of images that can be uploaded is 10. The rest images will be discarded'
|
|
608
|
-
);
|
|
609
|
-
return updatedArray.slice(0, 10);
|
|
610
|
-
}
|
|
611
|
-
return updatedArray;
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
}, [displayImages.length, processMedia]);
|
|
593
|
+
setMediaSelectorType('photo');
|
|
594
|
+
}, [displayImages.length]);
|
|
615
595
|
|
|
616
596
|
const onPressVideo = useCallback(async () => {
|
|
617
|
-
if (displayVideos.length
|
|
597
|
+
if (displayVideos.length >= 10)
|
|
618
598
|
return Alert.alert(
|
|
619
599
|
'Maximum upload limit reached',
|
|
620
600
|
"You've reached the upload limit of 10 videos. Any additional videos will not be saved."
|
|
621
601
|
);
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
)
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
return updatedArray;
|
|
602
|
+
setMediaSelectorType('video');
|
|
603
|
+
}, [displayVideos.length]);
|
|
604
|
+
|
|
605
|
+
// Receives new URIs from the custom media selector. Silently drops duplicates
|
|
606
|
+
// (already-selected files) and caps the total at 10, per PDT-4314.
|
|
607
|
+
const handleSelectedMedia = useCallback(
|
|
608
|
+
(uris: string[]) => {
|
|
609
|
+
const type = mediaSelectorType;
|
|
610
|
+
setMediaSelectorType(null);
|
|
611
|
+
if (!type || uris.length === 0) return;
|
|
612
|
+
const mediaOj = processMedia(uris);
|
|
613
|
+
if (!mediaOj) return;
|
|
614
|
+
const setter = type === 'photo' ? setDisplayImages : setDisplayVideos;
|
|
615
|
+
setter((prev) => {
|
|
616
|
+
const existingUrls = new Set(prev.map((m) => m.url));
|
|
617
|
+
const additions = mediaOj.filter((m) => !existingUrls.has(m.url));
|
|
618
|
+
const updated = [...prev, ...additions];
|
|
619
|
+
return updated.length > 10 ? updated.slice(0, 10) : updated;
|
|
642
620
|
});
|
|
643
|
-
}
|
|
644
|
-
|
|
621
|
+
},
|
|
622
|
+
[mediaSelectorType, processMedia]
|
|
623
|
+
);
|
|
645
624
|
|
|
646
625
|
const handleImageUploadError = useCallback(
|
|
647
626
|
(hasError: boolean, source: string) => {
|
|
@@ -752,6 +731,11 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
|
|
|
752
731
|
[]
|
|
753
732
|
);
|
|
754
733
|
|
|
734
|
+
// PDT-4310 / PDT-4312: at the 10-attachment cap the camera + gallery icons
|
|
735
|
+
// are disabled until the user removes a frame.
|
|
736
|
+
const isMediaCapReached =
|
|
737
|
+
displayImages.length >= 10 || displayVideos.length >= 10;
|
|
738
|
+
|
|
755
739
|
const renderDetailedAttachment = useCallback(() => {
|
|
756
740
|
if (isEditMode) return null;
|
|
757
741
|
if (shouldShowDetailAttachment) {
|
|
@@ -762,6 +746,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
|
|
|
762
746
|
onPressVideo={onPressVideo}
|
|
763
747
|
chosenMediaType={chosenMediaType}
|
|
764
748
|
onHeightChange={setAttachmentBarHeight}
|
|
749
|
+
disabled={isMediaCapReached}
|
|
765
750
|
/>
|
|
766
751
|
);
|
|
767
752
|
}
|
|
@@ -772,11 +757,13 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
|
|
|
772
757
|
onPressVideo={onPressVideo}
|
|
773
758
|
chosenMediaType={chosenMediaType}
|
|
774
759
|
onHeightChange={setAttachmentBarHeight}
|
|
760
|
+
disabled={isMediaCapReached}
|
|
775
761
|
/>
|
|
776
762
|
);
|
|
777
763
|
}, [
|
|
778
764
|
chosenMediaType,
|
|
779
765
|
isEditMode,
|
|
766
|
+
isMediaCapReached,
|
|
780
767
|
onPressCamera,
|
|
781
768
|
onPressImage,
|
|
782
769
|
onPressVideo,
|
|
@@ -835,54 +822,25 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
|
|
|
835
822
|
})}
|
|
836
823
|
<View style={styles.imageContainer}>
|
|
837
824
|
{displayImages.length > 0 && (
|
|
838
|
-
<
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
source={item.url}
|
|
847
|
-
onClose={handleOnCloseImage}
|
|
848
|
-
index={index} //TODO: Fix this without index
|
|
849
|
-
onLoadFinish={handleOnFinishImage}
|
|
850
|
-
onUploadError={handleImageUploadError}
|
|
851
|
-
isUploaded={item.isUploaded}
|
|
852
|
-
fileId={item.fileId}
|
|
853
|
-
fileCount={displayImages.length}
|
|
854
|
-
isEditMode={isEditMode}
|
|
855
|
-
postId={item.postId}
|
|
856
|
-
setIsUploading={setIsUploading}
|
|
857
|
-
/>
|
|
858
|
-
);
|
|
859
|
-
}}
|
|
860
|
-
numColumns={3}
|
|
825
|
+
<SelectedMediaComponent
|
|
826
|
+
mediaType="image"
|
|
827
|
+
media={displayImages}
|
|
828
|
+
onClose={handleOnCloseImage}
|
|
829
|
+
onLoadFinish={handleOnFinishImage}
|
|
830
|
+
onUploadError={handleImageUploadError}
|
|
831
|
+
isEditMode={isEditMode}
|
|
832
|
+
setIsUploading={setIsUploading}
|
|
861
833
|
/>
|
|
862
834
|
)}
|
|
863
835
|
{displayVideos.length > 0 && (
|
|
864
|
-
<
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
index={index} //TODO: Fix this without index
|
|
873
|
-
onLoadFinish={handleOnFinishVideo}
|
|
874
|
-
onUploadError={handleVideoUploadError}
|
|
875
|
-
isUploaded={item.isUploaded}
|
|
876
|
-
fileId={item.fileId}
|
|
877
|
-
thumbNail={item.thumbNail as string}
|
|
878
|
-
fileCount={displayVideos.length}
|
|
879
|
-
isEditMode={isEditMode}
|
|
880
|
-
postId={item.postId}
|
|
881
|
-
setIsUploading={setIsUploading}
|
|
882
|
-
/>
|
|
883
|
-
);
|
|
884
|
-
}}
|
|
885
|
-
numColumns={3}
|
|
836
|
+
<SelectedMediaComponent
|
|
837
|
+
mediaType="video"
|
|
838
|
+
media={displayVideos}
|
|
839
|
+
onClose={handleOnCloseVideo}
|
|
840
|
+
onLoadFinish={handleOnFinishVideo}
|
|
841
|
+
onUploadError={handleVideoUploadError}
|
|
842
|
+
isEditMode={isEditMode}
|
|
843
|
+
setIsUploading={setIsUploading}
|
|
886
844
|
/>
|
|
887
845
|
)}
|
|
888
846
|
</View>
|
|
@@ -902,6 +860,22 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
|
|
|
902
860
|
</View>
|
|
903
861
|
</KeyboardAvoidingView>
|
|
904
862
|
<StatusBar backgroundColor={themeStyles.colors.background} />
|
|
863
|
+
<MediaSelector
|
|
864
|
+
visible={mediaSelectorType !== null}
|
|
865
|
+
mediaType={mediaSelectorType ?? 'photo'}
|
|
866
|
+
existing={(mediaSelectorType === 'video'
|
|
867
|
+
? displayVideos
|
|
868
|
+
: displayImages
|
|
869
|
+
).map((m) => m.url)}
|
|
870
|
+
remaining={
|
|
871
|
+
10 -
|
|
872
|
+
(mediaSelectorType === 'video'
|
|
873
|
+
? displayVideos.length
|
|
874
|
+
: displayImages.length)
|
|
875
|
+
}
|
|
876
|
+
onClose={() => setMediaSelectorType(null)}
|
|
877
|
+
onConfirm={handleSelectedMedia}
|
|
878
|
+
/>
|
|
905
879
|
</SafeAreaView>
|
|
906
880
|
);
|
|
907
881
|
};
|