@amityco/react-native-social-uikit 4.1.0 → 4.1.1-11cdc854.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/lib/commonjs/core/assets/icons/xml.js +55 -1
  2. package/lib/commonjs/core/assets/icons/xml.js.map +1 -1
  3. package/lib/commonjs/core/hooks/usePostByIds.js +38 -0
  4. package/lib/commonjs/core/hooks/usePostByIds.js.map +1 -0
  5. package/lib/commonjs/social/components/LoadingImage/index.js +4 -3
  6. package/lib/commonjs/social/components/LoadingImage/index.js.map +1 -1
  7. package/lib/commonjs/social/components/LoadingImage/styles.js +4 -0
  8. package/lib/commonjs/social/components/LoadingImage/styles.js.map +1 -1
  9. package/lib/commonjs/social/components/LoadingVideo/index.js +12 -6
  10. package/lib/commonjs/social/components/LoadingVideo/index.js.map +1 -1
  11. package/lib/commonjs/social/components/LoadingVideo/styles.js +4 -0
  12. package/lib/commonjs/social/components/LoadingVideo/styles.js.map +1 -1
  13. package/lib/commonjs/social/components/MediaViewer/MediaViewer.js +266 -0
  14. package/lib/commonjs/social/components/MediaViewer/MediaViewer.js.map +1 -0
  15. package/lib/commonjs/social/components/MediaViewer/index.js +13 -0
  16. package/lib/commonjs/social/components/MediaViewer/index.js.map +1 -0
  17. package/lib/commonjs/social/components/MediaViewer/styles.js +116 -0
  18. package/lib/commonjs/social/components/MediaViewer/styles.js.map +1 -0
  19. package/lib/commonjs/social/components/PostContent/index.js +75 -120
  20. package/lib/commonjs/social/components/PostContent/index.js.map +1 -1
  21. package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js +180 -0
  22. package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -0
  23. package/lib/commonjs/social/components/SelectedMediaComponent/index.js +13 -0
  24. package/lib/commonjs/social/components/SelectedMediaComponent/index.js.map +1 -0
  25. package/lib/commonjs/social/components/SelectedMediaComponent/styles.js +43 -0
  26. package/lib/commonjs/social/components/SelectedMediaComponent/styles.js.map +1 -0
  27. package/lib/commonjs/social/constants/index.js +11 -0
  28. package/lib/commonjs/social/constants/index.js.map +1 -0
  29. package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js +280 -0
  30. package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js.map +1 -0
  31. package/lib/commonjs/social/elements/PostMediaElement/index.js +13 -0
  32. package/lib/commonjs/social/elements/PostMediaElement/index.js.map +1 -0
  33. package/lib/commonjs/social/elements/PostMediaElement/styles.js +117 -0
  34. package/lib/commonjs/social/elements/PostMediaElement/styles.js.map +1 -0
  35. package/lib/commonjs/social/features/post/Composer/PostComposer.js +91 -106
  36. package/lib/commonjs/social/features/post/Composer/PostComposer.js.map +1 -1
  37. package/lib/commonjs/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js +17 -7
  38. package/lib/commonjs/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js.map +1 -1
  39. package/lib/commonjs/social/features/post/components/MediaAttachment/MediaAttachment.js +17 -7
  40. package/lib/commonjs/social/features/post/components/MediaAttachment/MediaAttachment.js.map +1 -1
  41. package/lib/commonjs/social/utils/getFrameRatio.js +52 -0
  42. package/lib/commonjs/social/utils/getFrameRatio.js.map +1 -0
  43. package/lib/commonjs/social/utils/getIndicatorConfig.js +59 -0
  44. package/lib/commonjs/social/utils/getIndicatorConfig.js.map +1 -0
  45. package/lib/module/core/assets/icons/xml.js +48 -0
  46. package/lib/module/core/assets/icons/xml.js.map +1 -1
  47. package/lib/module/core/hooks/usePostByIds.js +32 -0
  48. package/lib/module/core/hooks/usePostByIds.js.map +1 -0
  49. package/lib/module/social/components/LoadingImage/index.js +4 -3
  50. package/lib/module/social/components/LoadingImage/index.js.map +1 -1
  51. package/lib/module/social/components/LoadingImage/styles.js +4 -0
  52. package/lib/module/social/components/LoadingImage/styles.js.map +1 -1
  53. package/lib/module/social/components/LoadingVideo/index.js +13 -7
  54. package/lib/module/social/components/LoadingVideo/index.js.map +1 -1
  55. package/lib/module/social/components/LoadingVideo/styles.js +4 -0
  56. package/lib/module/social/components/LoadingVideo/styles.js.map +1 -1
  57. package/lib/module/social/components/MediaViewer/MediaViewer.js +259 -0
  58. package/lib/module/social/components/MediaViewer/MediaViewer.js.map +1 -0
  59. package/lib/module/social/components/MediaViewer/index.js +2 -0
  60. package/lib/module/social/components/MediaViewer/index.js.map +1 -0
  61. package/lib/module/social/components/MediaViewer/styles.js +109 -0
  62. package/lib/module/social/components/MediaViewer/styles.js.map +1 -0
  63. package/lib/module/social/components/PostContent/index.js +77 -122
  64. package/lib/module/social/components/PostContent/index.js.map +1 -1
  65. package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js +174 -0
  66. package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -0
  67. package/lib/module/social/components/SelectedMediaComponent/index.js +2 -0
  68. package/lib/module/social/components/SelectedMediaComponent/index.js.map +1 -0
  69. package/lib/module/social/components/SelectedMediaComponent/styles.js +36 -0
  70. package/lib/module/social/components/SelectedMediaComponent/styles.js.map +1 -0
  71. package/lib/module/social/constants/index.js +5 -0
  72. package/lib/module/social/constants/index.js.map +1 -0
  73. package/lib/module/social/elements/PostMediaElement/PostMediaElement.js +273 -0
  74. package/lib/module/social/elements/PostMediaElement/PostMediaElement.js.map +1 -0
  75. package/lib/module/social/elements/PostMediaElement/index.js +2 -0
  76. package/lib/module/social/elements/PostMediaElement/index.js.map +1 -0
  77. package/lib/module/social/elements/PostMediaElement/styles.js +110 -0
  78. package/lib/module/social/elements/PostMediaElement/styles.js.map +1 -0
  79. package/lib/module/social/features/post/Composer/PostComposer.js +94 -109
  80. package/lib/module/social/features/post/Composer/PostComposer.js.map +1 -1
  81. package/lib/module/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js +17 -7
  82. package/lib/module/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.js.map +1 -1
  83. package/lib/module/social/features/post/components/MediaAttachment/MediaAttachment.js +17 -7
  84. package/lib/module/social/features/post/components/MediaAttachment/MediaAttachment.js.map +1 -1
  85. package/lib/module/social/utils/getFrameRatio.js +44 -0
  86. package/lib/module/social/utils/getFrameRatio.js.map +1 -0
  87. package/lib/module/social/utils/getIndicatorConfig.js +53 -0
  88. package/lib/module/social/utils/getIndicatorConfig.js.map +1 -0
  89. package/lib/typescript/core/assets/icons/xml.d.ts +7 -0
  90. package/lib/typescript/core/assets/icons/xml.d.ts.map +1 -1
  91. package/lib/typescript/core/hooks/usePostByIds.d.ts +8 -0
  92. package/lib/typescript/core/hooks/usePostByIds.d.ts.map +1 -0
  93. package/lib/typescript/core/types/mention.d.ts +6 -0
  94. package/lib/typescript/core/types/mention.d.ts.map +1 -1
  95. package/lib/typescript/social/components/LoadingImage/index.d.ts +2 -1
  96. package/lib/typescript/social/components/LoadingImage/index.d.ts.map +1 -1
  97. package/lib/typescript/social/components/LoadingImage/styles.d.ts +4 -0
  98. package/lib/typescript/social/components/LoadingImage/styles.d.ts.map +1 -1
  99. package/lib/typescript/social/components/LoadingVideo/index.d.ts +2 -1
  100. package/lib/typescript/social/components/LoadingVideo/index.d.ts.map +1 -1
  101. package/lib/typescript/social/components/LoadingVideo/styles.d.ts +4 -0
  102. package/lib/typescript/social/components/LoadingVideo/styles.d.ts.map +1 -1
  103. package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts +17 -0
  104. package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts.map +1 -0
  105. package/lib/typescript/social/components/MediaViewer/index.d.ts +3 -0
  106. package/lib/typescript/social/components/MediaViewer/index.d.ts.map +1 -0
  107. package/lib/typescript/social/components/MediaViewer/styles.d.ts +99 -0
  108. package/lib/typescript/social/components/MediaViewer/styles.d.ts.map +1 -0
  109. package/lib/typescript/social/components/PostContent/index.d.ts.map +1 -1
  110. package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts +12 -0
  111. package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts.map +1 -0
  112. package/lib/typescript/social/components/SelectedMediaComponent/index.d.ts +3 -0
  113. package/lib/typescript/social/components/SelectedMediaComponent/index.d.ts.map +1 -0
  114. package/lib/typescript/social/components/SelectedMediaComponent/styles.d.ts +25 -0
  115. package/lib/typescript/social/components/SelectedMediaComponent/styles.d.ts.map +1 -0
  116. package/lib/typescript/social/constants/index.d.ts +4 -0
  117. package/lib/typescript/social/constants/index.d.ts.map +1 -0
  118. package/lib/typescript/social/elements/PostMediaElement/PostMediaElement.d.ts +15 -0
  119. package/lib/typescript/social/elements/PostMediaElement/PostMediaElement.d.ts.map +1 -0
  120. package/lib/typescript/social/elements/PostMediaElement/index.d.ts +3 -0
  121. package/lib/typescript/social/elements/PostMediaElement/index.d.ts.map +1 -0
  122. package/lib/typescript/social/elements/PostMediaElement/styles.d.ts +88 -0
  123. package/lib/typescript/social/elements/PostMediaElement/styles.d.ts.map +1 -0
  124. package/lib/typescript/social/features/post/Composer/PostComposer.d.ts.map +1 -1
  125. package/lib/typescript/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.d.ts +3 -0
  126. package/lib/typescript/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.d.ts.map +1 -1
  127. package/lib/typescript/social/features/post/components/MediaAttachment/MediaAttachment.d.ts +3 -0
  128. package/lib/typescript/social/features/post/components/MediaAttachment/MediaAttachment.d.ts.map +1 -1
  129. package/lib/typescript/social/utils/getFrameRatio.d.ts +21 -0
  130. package/lib/typescript/social/utils/getFrameRatio.d.ts.map +1 -0
  131. package/lib/typescript/social/utils/getIndicatorConfig.d.ts +11 -0
  132. package/lib/typescript/social/utils/getIndicatorConfig.d.ts.map +1 -0
  133. package/package.json +3 -3
  134. package/src/core/assets/icons/xml.ts +60 -0
  135. package/src/core/hooks/usePostByIds.ts +33 -0
  136. package/src/core/types/mention.ts +6 -0
  137. package/src/social/components/LoadingImage/index.tsx +12 -2
  138. package/src/social/components/LoadingImage/styles.ts +4 -0
  139. package/src/social/components/LoadingVideo/index.tsx +28 -4
  140. package/src/social/components/LoadingVideo/styles.ts +4 -0
  141. package/src/social/components/MediaViewer/MediaViewer.tsx +384 -0
  142. package/src/social/components/MediaViewer/index.tsx +2 -0
  143. package/src/social/components/MediaViewer/styles.ts +109 -0
  144. package/src/social/components/PostContent/index.tsx +86 -159
  145. package/src/social/components/SelectedMediaComponent/SelectedMediaComponent.tsx +236 -0
  146. package/src/social/components/SelectedMediaComponent/index.ts +2 -0
  147. package/src/social/components/SelectedMediaComponent/styles.ts +38 -0
  148. package/src/social/constants/index.ts +4 -0
  149. package/src/social/elements/PostMediaElement/PostMediaElement.tsx +362 -0
  150. package/src/social/elements/PostMediaElement/index.tsx +5 -0
  151. package/src/social/elements/PostMediaElement/styles.ts +99 -0
  152. package/src/social/features/post/Composer/PostComposer.tsx +107 -123
  153. package/src/social/features/post/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx +10 -3
  154. package/src/social/features/post/components/MediaAttachment/MediaAttachment.tsx +14 -3
  155. package/src/social/utils/getFrameRatio.ts +42 -0
  156. package/src/social/utils/getIndicatorConfig.ts +62 -0
@@ -0,0 +1,362 @@
1
+ import {
2
+ useEffect,
3
+ useMemo,
4
+ useRef,
5
+ useState,
6
+ type MutableRefObject,
7
+ } from 'react';
8
+ import {
9
+ Animated,
10
+ Image,
11
+ ImageStyle,
12
+ LayoutChangeEvent,
13
+ NativeScrollEvent,
14
+ NativeSyntheticEvent,
15
+ Pressable,
16
+ ScrollView,
17
+ StyleProp,
18
+ useWindowDimensions,
19
+ View,
20
+ } from 'react-native';
21
+ import { SvgXml } from 'react-native-svg';
22
+ import { Typography } from '../../../core/components/Typography/Typography';
23
+ import useAuth from '../../../core/hooks/useAuth';
24
+ import {
25
+ videoControlIcon,
26
+ brokenImageIcon,
27
+ } from '../../../core/assets/icons/xml';
28
+ import {
29
+ getFrameRatio,
30
+ getVideoDisplayDims,
31
+ FRAME_RATIO_VALUE,
32
+ DEFAULT_FRAME_RATIO,
33
+ type FrameRatio,
34
+ } from '../../utils/getFrameRatio';
35
+ import { getIndicatorConfig } from '../../utils/getIndicatorConfig';
36
+ import { useStyles } from './styles';
37
+
38
+ type MediaPost = Amity.Post<'image'> | Amity.Post<'video'>;
39
+
40
+ function isMediaPost(post: Amity.Post): post is MediaPost {
41
+ return post.dataType === 'image' || post.dataType === 'video';
42
+ }
43
+
44
+ function getAttachmentRatio(post?: MediaPost): FrameRatio {
45
+ if (!post) return DEFAULT_FRAME_RATIO;
46
+
47
+ if (post.dataType === 'image') {
48
+ const image = post.getImageInfo();
49
+ return getFrameRatio(image?.getWidth(), image?.getHeight());
50
+ }
51
+
52
+ const video = post.getVideoInfo();
53
+ const { width, height } = getVideoDisplayDims({
54
+ width: video?.getWidth(),
55
+ height: video?.getHeight(),
56
+ rotation: video?.getRotation(),
57
+ });
58
+ return getFrameRatio(width, height);
59
+ }
60
+
61
+ export type PostMediaControls = {
62
+ slideTo: (index: number) => void;
63
+ };
64
+
65
+ export type PostMediaElementProps = {
66
+ posts: Amity.Post[];
67
+ onImageClick: (index: number) => void;
68
+ onVideoClick: (index: number) => void;
69
+ controlsRef?: MutableRefObject<PostMediaControls | null>;
70
+ /** While the post's media children are still being fetched, show a
71
+ * placeholder frame instead of nothing (avoids a blank → pop). */
72
+ loading?: boolean;
73
+ };
74
+
75
+ export function PostMediaElement({
76
+ posts,
77
+ onImageClick,
78
+ onVideoClick,
79
+ controlsRef,
80
+ loading = false,
81
+ }: PostMediaElementProps) {
82
+ const styles = useStyles();
83
+ const { width: screenWidth } = useWindowDimensions();
84
+ const [current, setCurrent] = useState(0);
85
+ const [trackWidth, setTrackWidth] = useState(0);
86
+ const scrollRef = useRef<ScrollView>(null);
87
+
88
+ const mediaPosts = useMemo(() => (posts ?? []).filter(isMediaPost), [posts]);
89
+ const ratio = useMemo(() => getAttachmentRatio(mediaPosts[0]), [mediaPosts]);
90
+
91
+ if (mediaPosts.length === 0) {
92
+ if (!loading) return null;
93
+ // Loading placeholder: a full-bleed frame in the base-shade4 colour, so the
94
+ // media area doesn't flash blank before the child posts resolve.
95
+ return (
96
+ <View style={styles.postMedia}>
97
+ <View style={styles.postMedia__track}>
98
+ <View
99
+ style={[
100
+ styles.postMedia__frame,
101
+ { aspectRatio: FRAME_RATIO_VALUE[DEFAULT_FRAME_RATIO] },
102
+ ]}
103
+ />
104
+ </View>
105
+ </View>
106
+ );
107
+ }
108
+
109
+ const total = mediaPosts.length;
110
+ const isCarousel = total > 1;
111
+ const aspectRatio = FRAME_RATIO_VALUE[ratio];
112
+ // The carousel is full-bleed (screen width); use that immediately so the
113
+ // ScrollView renders without waiting for onLayout — otherwise there is an
114
+ // empty block between the placeholder and the first frame.
115
+ const slideWidth = trackWidth || screenWidth;
116
+
117
+ const handleFrameClick = (index: number) => {
118
+ const post = mediaPosts[index];
119
+ if (post.dataType === 'image') onImageClick(index);
120
+ else onVideoClick(index);
121
+ };
122
+
123
+ const onLayout = (event: LayoutChangeEvent) => {
124
+ const width = event.nativeEvent.layout.width;
125
+ if (width && width !== trackWidth) setTrackWidth(width);
126
+ };
127
+
128
+ const onMomentumScrollEnd = (
129
+ event: NativeSyntheticEvent<NativeScrollEvent>
130
+ ) => {
131
+ if (!slideWidth) return;
132
+ const nextIndex = Math.round(
133
+ event.nativeEvent.contentOffset.x / slideWidth
134
+ );
135
+ if (nextIndex !== current) setCurrent(nextIndex);
136
+ };
137
+
138
+ if (controlsRef) {
139
+ controlsRef.current = {
140
+ slideTo: (index: number) => {
141
+ scrollRef.current?.scrollTo({
142
+ x: index * slideWidth,
143
+ animated: false,
144
+ });
145
+ setCurrent(index);
146
+ },
147
+ };
148
+ }
149
+
150
+ if (!isCarousel) {
151
+ return (
152
+ <View style={styles.postMedia}>
153
+ <View style={styles.postMedia__track} onLayout={onLayout}>
154
+ <MediaFrame
155
+ post={mediaPosts[0]}
156
+ aspectRatio={aspectRatio}
157
+ onPress={() => handleFrameClick(0)}
158
+ />
159
+ </View>
160
+ </View>
161
+ );
162
+ }
163
+
164
+ return (
165
+ <View style={styles.postMedia}>
166
+ <View style={styles.postMedia__track} onLayout={onLayout}>
167
+ <ScrollView
168
+ ref={scrollRef}
169
+ horizontal
170
+ pagingEnabled
171
+ showsHorizontalScrollIndicator={false}
172
+ onMomentumScrollEnd={onMomentumScrollEnd}
173
+ scrollEventThrottle={16}
174
+ decelerationRate="fast"
175
+ >
176
+ {mediaPosts.map((post, index) => (
177
+ <View
178
+ key={post.postId}
179
+ style={[styles.postMedia__slide, { width: slideWidth }]}
180
+ >
181
+ <MediaFrame
182
+ post={post}
183
+ aspectRatio={aspectRatio}
184
+ onPress={() => handleFrameClick(index)}
185
+ />
186
+ </View>
187
+ ))}
188
+ </ScrollView>
189
+ <PositionCounter current={current} total={total} />
190
+ </View>
191
+ <PaginationIndicator current={current} total={total} />
192
+ </View>
193
+ );
194
+ }
195
+
196
+ type PaginationIndicatorProps = {
197
+ current: number;
198
+ total: number;
199
+ };
200
+
201
+ function PaginationIndicator({ current, total }: PaginationIndicatorProps) {
202
+ const styles = useStyles();
203
+ const { dots } = getIndicatorConfig(current, total);
204
+
205
+ return (
206
+ <View style={styles.postMedia__indicator}>
207
+ {dots.map((dot) =>
208
+ dot.state === 'edge' ? (
209
+ <EdgeDot key={dot.key} side={dot.side} />
210
+ ) : (
211
+ <View key={dot.key} style={styles.postMedia__dotSlot}>
212
+ <View
213
+ style={[
214
+ styles.postMedia__dot,
215
+ dot.state === 'active'
216
+ ? styles.postMedia__dotActive
217
+ : styles.postMedia__dotInactive,
218
+ ]}
219
+ />
220
+ </View>
221
+ )
222
+ )}
223
+ </View>
224
+ );
225
+ }
226
+
227
+ // The strip keeps a constant width (fixed-width slots, always 6 elements), so
228
+ // an edge dot only needs to slide in from its own side over 300ms when it
229
+ // first appears (REQ-013b).
230
+ function EdgeDot({ side }: { side?: 'left' | 'right' }) {
231
+ const styles = useStyles();
232
+ const anim = useRef(new Animated.Value(0)).current;
233
+
234
+ useEffect(() => {
235
+ const animation = Animated.timing(anim, {
236
+ toValue: 1,
237
+ duration: 300,
238
+ useNativeDriver: true,
239
+ });
240
+ animation.start();
241
+ return () => animation.stop();
242
+ }, [anim]);
243
+
244
+ const translateX = anim.interpolate({
245
+ inputRange: [0, 1],
246
+ outputRange: [side === 'left' ? -6 : 6, 0],
247
+ });
248
+
249
+ return (
250
+ <View style={styles.postMedia__dotSlot}>
251
+ <Animated.View
252
+ style={[
253
+ styles.postMedia__dot,
254
+ styles.postMedia__dotEdge,
255
+ { opacity: anim, transform: [{ translateX }] },
256
+ ]}
257
+ />
258
+ </View>
259
+ );
260
+ }
261
+
262
+ type PositionCounterProps = {
263
+ current: number;
264
+ total: number;
265
+ };
266
+
267
+ function PositionCounter({ current, total }: PositionCounterProps) {
268
+ const styles = useStyles();
269
+
270
+ return (
271
+ <View style={styles.postMedia__counter}>
272
+ <Typography.Body style={styles.postMedia__counterLabel}>
273
+ {current + 1}/{total}
274
+ </Typography.Body>
275
+ </View>
276
+ );
277
+ }
278
+
279
+ type MediaFrameProps = {
280
+ post: MediaPost;
281
+ aspectRatio: number;
282
+ onPress: () => void;
283
+ };
284
+
285
+ function MediaFrame({ post, aspectRatio, onPress }: MediaFrameProps) {
286
+ const styles = useStyles();
287
+
288
+ return (
289
+ <Pressable
290
+ onPress={onPress}
291
+ style={[styles.postMedia__frame, { aspectRatio }]}
292
+ >
293
+ {post.dataType === 'image' ? (
294
+ <ImageMedia post={post as Amity.Post<'image'>} />
295
+ ) : (
296
+ <VideoMedia post={post as Amity.Post<'video'>} />
297
+ )}
298
+ </Pressable>
299
+ );
300
+ }
301
+
302
+ type ImageMediaProps = { post: Amity.Post<'image'> };
303
+
304
+ function ImageMedia({ post }: ImageMediaProps) {
305
+ const styles = useStyles();
306
+ const { apiRegion } = useAuth();
307
+ const [isBroken, setIsBroken] = useState(false);
308
+ const fileId = post.getImageInfo()?.fileId;
309
+ const url = fileId
310
+ ? `https://api.${apiRegion}.amity.co/api/v3/files/${fileId}/download?size=large`
311
+ : undefined;
312
+
313
+ if (!url || isBroken) return <BrokenFrame />;
314
+
315
+ return (
316
+ <Image
317
+ style={styles.postMedia__media as StyleProp<ImageStyle>}
318
+ source={{ uri: url }}
319
+ resizeMode="cover"
320
+ onError={() => setIsBroken(true)}
321
+ />
322
+ );
323
+ }
324
+
325
+ type VideoMediaProps = { post: Amity.Post<'video'> };
326
+
327
+ function VideoMedia({ post }: VideoMediaProps) {
328
+ const styles = useStyles();
329
+ const { apiRegion } = useAuth();
330
+ const [isBroken, setIsBroken] = useState(false);
331
+
332
+ const data = post.data as Amity.ContentDataVideo | undefined;
333
+ const thumbnailFileId = data?.thumbnailFileId;
334
+ const url = thumbnailFileId
335
+ ? `https://api.${apiRegion}.amity.co/api/v3/files/${thumbnailFileId}/download?size=large`
336
+ : undefined;
337
+
338
+ if (!url || isBroken) return <BrokenFrame />;
339
+
340
+ return (
341
+ <>
342
+ <Image
343
+ style={styles.postMedia__media as StyleProp<ImageStyle>}
344
+ source={{ uri: url }}
345
+ resizeMode="cover"
346
+ onError={() => setIsBroken(true)}
347
+ />
348
+ <View style={styles.postMedia__playButton}>
349
+ <SvgXml xml={videoControlIcon} width="40" height="40" />
350
+ </View>
351
+ </>
352
+ );
353
+ }
354
+
355
+ function BrokenFrame() {
356
+ const styles = useStyles();
357
+ return (
358
+ <View style={styles.postMedia__broken}>
359
+ <SvgXml xml={brokenImageIcon()} width={32} height={32} />
360
+ </View>
361
+ );
362
+ }
@@ -0,0 +1,5 @@
1
+ export { PostMediaElement } from './PostMediaElement';
2
+ export type {
3
+ PostMediaElementProps,
4
+ PostMediaControls,
5
+ } from './PostMediaElement';
@@ -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
+ };