@technotoil/image-video-editor 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/android/src/main/java/com/technotoil/image_videoeditor/FrameGrabberModule.kt +2 -6
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaEditorModule.kt +75 -35
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaLibraryModule.kt +51 -35
- package/android/src/main/java/com/technotoil/image_videoeditor/RNVideoPreviewManager.kt +98 -117
- package/ios/RNMediaEditor.m +38 -7
- package/ios/RNMediaLibrary.m +19 -15
- package/ios/RNVideoPreviewManager.m +2 -0
- package/lib/commonjs/assets/frames/film_vintage.png +0 -0
- package/lib/commonjs/assets/frames/floral_gold.png +0 -0
- package/lib/commonjs/assets/frames/minimal_double.png +0 -0
- package/lib/commonjs/assets/frames/polaroid_white.png +0 -0
- package/lib/commonjs/assets/frames/watercolor_floral.png +0 -0
- package/lib/commonjs/components/VideoEditor.js +235 -0
- package/lib/commonjs/components/VideoEditor.js.map +1 -0
- package/lib/commonjs/index.js +14 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/native/CameraView.js +109 -0
- package/lib/commonjs/native/CameraView.js.map +1 -0
- package/lib/commonjs/native/FrameGrabber.js +17 -0
- package/lib/commonjs/native/FrameGrabber.js.map +1 -0
- package/lib/commonjs/native/MediaEditor.js +24 -0
- package/lib/commonjs/native/MediaEditor.js.map +1 -0
- package/lib/commonjs/native/MediaLibrary.js +45 -0
- package/lib/commonjs/native/MediaLibrary.js.map +1 -0
- package/lib/commonjs/native/MediaPicker.js +17 -0
- package/lib/commonjs/native/MediaPicker.js.map +1 -0
- package/lib/commonjs/native/MediaPlayer.js +17 -0
- package/lib/commonjs/native/MediaPlayer.js.map +1 -0
- package/lib/commonjs/native/VideoPreview.js +17 -0
- package/lib/commonjs/native/VideoPreview.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/screens/CropScreen.js +1233 -0
- package/lib/commonjs/screens/CropScreen.js.map +1 -0
- package/lib/commonjs/screens/EditorScreen.js +6043 -0
- package/lib/commonjs/screens/EditorScreen.js.map +1 -0
- package/lib/commonjs/screens/ExportScreen.js +294 -0
- package/lib/commonjs/screens/ExportScreen.js.map +1 -0
- package/lib/commonjs/screens/GalleryScreen.js +510 -0
- package/lib/commonjs/screens/GalleryScreen.js.map +1 -0
- package/lib/commonjs/screens/PickScreen.js +1353 -0
- package/lib/commonjs/screens/PickScreen.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/components/VideoEditor.js +104 -31
- package/lib/module/components/VideoEditor.js.map +1 -1
- package/lib/module/screens/CropScreen.js +26 -9
- package/lib/module/screens/CropScreen.js.map +1 -1
- package/lib/module/screens/EditorScreen.js +371 -86
- package/lib/module/screens/EditorScreen.js.map +1 -1
- package/lib/module/screens/PickScreen.js +245 -93
- package/lib/module/screens/PickScreen.js.map +1 -1
- package/lib/typescript/src/components/VideoEditor.d.ts +18 -2
- package/lib/typescript/src/screens/CropScreen.d.ts +3 -1
- package/lib/typescript/src/screens/EditorScreen.d.ts +2 -1
- package/lib/typescript/src/screens/PickScreen.d.ts +6 -1
- package/lib/typescript/src/types.d.ts +1 -0
- package/package.json +17 -8
- package/src/components/VideoEditor.tsx +82 -11
- package/src/screens/CropScreen.tsx +54 -33
- package/src/screens/EditorScreen.tsx +366 -106
- package/src/screens/PickScreen.tsx +231 -76
- package/src/types.ts +1 -0
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
StyleSheet,
|
|
13
13
|
Text,
|
|
14
14
|
View,
|
|
15
|
+
Dimensions,
|
|
15
16
|
} from 'react-native';
|
|
16
17
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
17
18
|
import ImagePicker from 'react-native-image-crop-picker';
|
|
@@ -41,6 +42,7 @@ function formatDuration(ms?: number) {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export function PickScreen({
|
|
45
|
+
isActive = true,
|
|
44
46
|
items,
|
|
45
47
|
onPicked,
|
|
46
48
|
onNext,
|
|
@@ -50,6 +52,10 @@ export function PickScreen({
|
|
|
50
52
|
cameraModes = ['POST', 'STORY', 'REEL'],
|
|
51
53
|
onCameraModeChange,
|
|
52
54
|
defaultCameraMode,
|
|
55
|
+
maxSelection = 1,
|
|
56
|
+
aspectRatio = 'free',
|
|
57
|
+
mediaType = 'any',
|
|
58
|
+
mediaTabs = ['GALLERY', 'PHOTO', 'VIDEO'],
|
|
53
59
|
}: {
|
|
54
60
|
items: MediaItem[];
|
|
55
61
|
onPicked: (items: MediaItem[]) => void;
|
|
@@ -60,9 +66,14 @@ export function PickScreen({
|
|
|
60
66
|
cameraModes?: string[];
|
|
61
67
|
onCameraModeChange?: (mode: string) => void;
|
|
62
68
|
defaultCameraMode?: string;
|
|
69
|
+
maxSelection?: number;
|
|
70
|
+
aspectRatio?: '1:1' | '4:3' | '4:5' | '16:9' | '9:16' | 'free';
|
|
71
|
+
mediaType?: 'photo' | 'video' | 'any';
|
|
72
|
+
mediaTabs?: ('GALLERY' | 'PHOTO' | 'VIDEO')[];
|
|
73
|
+
isActive?: boolean;
|
|
63
74
|
}) {
|
|
64
75
|
const insets = useSafeAreaInsets();
|
|
65
|
-
const [tab, setTab] = useState<'GALLERY' | 'PHOTO' | 'VIDEO'>('GALLERY');
|
|
76
|
+
const [tab, setTab] = useState<'GALLERY' | 'PHOTO' | 'VIDEO'>(mediaTabs[0] || 'GALLERY');
|
|
66
77
|
const [activeAlbum, setActiveAlbum] = useState<Album>({ id: 'all', title: 'Recents' });
|
|
67
78
|
const [showAlbumPicker, setShowAlbumPicker] = useState(false);
|
|
68
79
|
const [postType, setPostType] = useState<'POST' | 'STORY' | 'REEL'>('POST');
|
|
@@ -73,7 +84,21 @@ export function PickScreen({
|
|
|
73
84
|
const [albums, setAlbums] = useState<Album[]>([]);
|
|
74
85
|
const [loading, setLoading] = useState(false);
|
|
75
86
|
const [previewUri, setPreviewUri] = useState<string | null>(null);
|
|
76
|
-
|
|
87
|
+
|
|
88
|
+
// Aspect ratio logic
|
|
89
|
+
const isRatioLocked = aspectRatio !== 'free';
|
|
90
|
+
const ratioMap: Record<string, number> = {
|
|
91
|
+
'1:1': 1,
|
|
92
|
+
'4:3': 4 / 3,
|
|
93
|
+
'4:5': 4 / 5,
|
|
94
|
+
'16:9': 16 / 9,
|
|
95
|
+
'9:16': 9 / 16,
|
|
96
|
+
};
|
|
97
|
+
const previewAspectRatio = isRatioLocked ? ratioMap[aspectRatio] ?? 1 : undefined;
|
|
98
|
+
// When ratio is locked, always use 'cover' (fills the fixed frame)
|
|
99
|
+
const [cropMode, setCropMode] = useState<'1:1' | 'original'>(
|
|
100
|
+
isRatioLocked ? '1:1' : '1:1'
|
|
101
|
+
);
|
|
77
102
|
const [videoPaused, setVideoPaused] = useState(false);
|
|
78
103
|
const scaleAnim = useRef(new Animated.Value(1)).current;
|
|
79
104
|
|
|
@@ -158,18 +183,21 @@ export function PickScreen({
|
|
|
158
183
|
|
|
159
184
|
const loadMedia = async (albumId?: string) => {
|
|
160
185
|
try {
|
|
186
|
+
console.log('loadMedia called with albumId:', albumId);
|
|
161
187
|
setLoading(true);
|
|
162
188
|
const assets = await listMedia({
|
|
163
189
|
limit: 200,
|
|
164
190
|
offset: 0,
|
|
165
|
-
type: 'all',
|
|
191
|
+
type: mediaType === 'photo' ? 'image' : mediaType === 'video' ? 'video' : 'all',
|
|
166
192
|
albumId: albumId === 'all' ? undefined : albumId,
|
|
167
193
|
});
|
|
194
|
+
console.log('loadMedia got assets:', assets.length);
|
|
168
195
|
setLibrary(assets);
|
|
169
196
|
if (assets[0] && !multiSelect) {
|
|
170
197
|
setSelectedMedia(assets[0]);
|
|
171
198
|
}
|
|
172
199
|
} catch (err: any) {
|
|
200
|
+
console.error('loadMedia error:', err);
|
|
173
201
|
Alert.alert('Library error', err?.message ?? 'Failed to load library.');
|
|
174
202
|
} finally {
|
|
175
203
|
setLoading(false);
|
|
@@ -177,9 +205,11 @@ export function PickScreen({
|
|
|
177
205
|
};
|
|
178
206
|
|
|
179
207
|
useEffect(() => {
|
|
208
|
+
console.log('PickScreen mounted!');
|
|
180
209
|
(async () => {
|
|
181
210
|
try {
|
|
182
211
|
const ok = await requestMediaAccess();
|
|
212
|
+
console.log('requestMediaAccess ok?', ok);
|
|
183
213
|
if (!ok) {
|
|
184
214
|
Alert.alert('Permission needed', 'Allow photo access to continue.');
|
|
185
215
|
return;
|
|
@@ -206,6 +236,20 @@ export function PickScreen({
|
|
|
206
236
|
return [cameraItem, ...list];
|
|
207
237
|
}, [library, tab]);
|
|
208
238
|
|
|
239
|
+
const listData = useMemo(() => {
|
|
240
|
+
const rows = Array.from({ length: Math.ceil(filtered.length / 4) }).map((_, i) => ({
|
|
241
|
+
id: `row_${i}`,
|
|
242
|
+
type: 'row',
|
|
243
|
+
items: filtered.slice(i * 4, i * 4 + 4),
|
|
244
|
+
}));
|
|
245
|
+
|
|
246
|
+
return [
|
|
247
|
+
{ id: 'header_preview', type: 'preview' },
|
|
248
|
+
{ id: 'header_albumRow', type: 'albumRow' },
|
|
249
|
+
...rows
|
|
250
|
+
];
|
|
251
|
+
}, [filtered]);
|
|
252
|
+
|
|
209
253
|
useEffect(() => {
|
|
210
254
|
let cancelled = false;
|
|
211
255
|
setVideoPaused(false);
|
|
@@ -218,6 +262,7 @@ export function PickScreen({
|
|
|
218
262
|
setPreviewUri(selectedMedia.uri);
|
|
219
263
|
return;
|
|
220
264
|
}
|
|
265
|
+
setPreviewUri(null);
|
|
221
266
|
try {
|
|
222
267
|
const fileUri = await exportAsset(selectedMedia.id);
|
|
223
268
|
if (!cancelled) setPreviewUri(fileUri);
|
|
@@ -232,25 +277,36 @@ export function PickScreen({
|
|
|
232
277
|
|
|
233
278
|
const playableUri = useMemo(() => {
|
|
234
279
|
if (!selectedMedia) return null;
|
|
235
|
-
if (previewUri
|
|
236
|
-
|
|
237
|
-
return null;
|
|
280
|
+
if (previewUri) return previewUri;
|
|
281
|
+
return selectedMedia.uri;
|
|
238
282
|
}, [previewUri, selectedMedia]);
|
|
239
283
|
|
|
240
284
|
const toggleMultiSelect = () => {
|
|
241
|
-
setMultiSelect((v) =>
|
|
242
|
-
|
|
285
|
+
setMultiSelect((v) => {
|
|
286
|
+
if (!v && selectedMedia) {
|
|
287
|
+
setSelectedItems([selectedMedia]);
|
|
288
|
+
} else {
|
|
289
|
+
setSelectedItems([]);
|
|
290
|
+
}
|
|
291
|
+
return !v;
|
|
292
|
+
});
|
|
243
293
|
};
|
|
244
294
|
|
|
245
295
|
const handleSelectItem = (item: MediaItem) => {
|
|
246
|
-
setSelectedMedia(item);
|
|
247
296
|
if (multiSelect) {
|
|
248
297
|
setSelectedItems((prev) => {
|
|
249
298
|
const exists = prev.find((i) => i.id === item.id);
|
|
250
|
-
if (exists)
|
|
251
|
-
|
|
299
|
+
if (exists) {
|
|
300
|
+
const newItems = prev.filter((i) => i.id !== item.id);
|
|
301
|
+
setSelectedMedia(newItems.length > 0 ? newItems[newItems.length - 1] : null);
|
|
302
|
+
return newItems;
|
|
303
|
+
}
|
|
304
|
+
if (prev.length >= maxSelection) return prev;
|
|
305
|
+
setSelectedMedia(item);
|
|
252
306
|
return [...prev, item];
|
|
253
307
|
});
|
|
308
|
+
} else {
|
|
309
|
+
setSelectedMedia(item);
|
|
254
310
|
}
|
|
255
311
|
|
|
256
312
|
Animated.sequence([
|
|
@@ -479,82 +535,167 @@ export function PickScreen({
|
|
|
479
535
|
</View>
|
|
480
536
|
</Modal>
|
|
481
537
|
|
|
482
|
-
<Animated.View style={[styles.preview, { transform: [{ scale: scaleAnim }] }]}>
|
|
483
|
-
{selectedMedia?.type === 'video' ? (
|
|
484
|
-
<Pressable style={styles.previewImage} onPress={() => setVideoPaused((v) => !v)}>
|
|
485
|
-
{playableUri ? (
|
|
486
|
-
<VideoPreview
|
|
487
|
-
uri={playableUri}
|
|
488
|
-
paused={videoPaused}
|
|
489
|
-
muted={false}
|
|
490
|
-
style={styles.previewImage}
|
|
491
|
-
resizeMode={cropMode === '1:1' ? 'cover' : 'contain'}
|
|
492
|
-
/>
|
|
493
|
-
) : (
|
|
494
|
-
<Image
|
|
495
|
-
source={{ uri: selectedMedia.thumbnailUri || selectedMedia.uri }}
|
|
496
|
-
style={styles.previewImage}
|
|
497
|
-
resizeMode="cover"
|
|
498
|
-
/>
|
|
499
|
-
)}
|
|
500
|
-
<View style={styles.previewOverlay}>
|
|
501
|
-
<View style={styles.playPauseCircle}>
|
|
502
|
-
<Ionicons name={videoPaused ? 'play' : 'pause'} size={24} color="#fff" />
|
|
503
|
-
</View>
|
|
504
|
-
</View>
|
|
505
|
-
</Pressable>
|
|
506
|
-
) : (
|
|
507
|
-
<Image
|
|
508
|
-
source={{ uri: previewUri ?? selectedMedia?.uri }}
|
|
509
|
-
style={[styles.previewImage, cropMode === '1:1' ? styles.squareCrop : styles.originalCrop]}
|
|
510
|
-
resizeMode={cropMode === '1:1' ? 'cover' : 'contain'}
|
|
511
|
-
/>
|
|
512
|
-
)}
|
|
513
|
-
|
|
514
|
-
<View style={styles.previewControls}>
|
|
515
|
-
<Pressable
|
|
516
|
-
style={styles.cropToggle}
|
|
517
|
-
onPress={() => setCropMode((v) => (v === '1:1' ? 'original' : '1:1'))}
|
|
518
|
-
>
|
|
519
|
-
<Ionicons
|
|
520
|
-
name={cropMode === '1:1' ? 'square-outline' : 'expand-outline'}
|
|
521
|
-
size={20}
|
|
522
|
-
color="#fff"
|
|
523
|
-
/>
|
|
524
|
-
</Pressable>
|
|
525
|
-
</View>
|
|
526
|
-
</Animated.View>
|
|
527
|
-
|
|
528
|
-
<View style={styles.albumRow}>
|
|
529
|
-
<Pressable
|
|
530
|
-
style={styles.albumSelector}
|
|
531
|
-
onPress={() => setShowAlbumPicker((v) => !v)}
|
|
532
|
-
>
|
|
533
|
-
<Text style={styles.albumTitle}>{activeAlbum.title}</Text>
|
|
534
|
-
<Ionicons name="chevron-down" size={16} color="#fff" style={{ marginLeft: 6 }} />
|
|
535
|
-
</Pressable>
|
|
536
|
-
|
|
537
|
-
<Pressable style={[styles.multiSelectBtn, multiSelect && styles.multiSelectBtnActive]} onPress={toggleMultiSelect}>
|
|
538
|
-
<Text style={[styles.multiSelectText, multiSelect && styles.multiSelectTextActive]}>{multiSelect ? 'Done' : 'Select'}</Text>
|
|
539
|
-
</Pressable>
|
|
540
|
-
</View>
|
|
541
|
-
|
|
542
538
|
<FlatList
|
|
543
|
-
data={
|
|
539
|
+
data={listData}
|
|
540
|
+
extraData={{
|
|
541
|
+
selectedMedia,
|
|
542
|
+
previewUri,
|
|
543
|
+
playableUri,
|
|
544
|
+
videoPaused,
|
|
545
|
+
cropMode,
|
|
546
|
+
isRatioLocked,
|
|
547
|
+
activeAlbum,
|
|
548
|
+
multiSelect,
|
|
549
|
+
selectedItems,
|
|
550
|
+
showAlbumPicker,
|
|
551
|
+
loading
|
|
552
|
+
}}
|
|
544
553
|
keyExtractor={(item) => item.id}
|
|
545
|
-
|
|
554
|
+
stickyHeaderIndices={[1]}
|
|
546
555
|
style={styles.libraryList}
|
|
547
|
-
renderItem={renderThumb}
|
|
548
556
|
contentContainerStyle={styles.grid}
|
|
557
|
+
getItemLayout={(data, index) => {
|
|
558
|
+
const previewHeight = isRatioLocked && previewAspectRatio
|
|
559
|
+
? Dimensions.get('window').width / previewAspectRatio
|
|
560
|
+
: 420;
|
|
561
|
+
const albumRowHeight = 48;
|
|
562
|
+
const rowHeight = Dimensions.get('window').width / 4;
|
|
563
|
+
|
|
564
|
+
if (index === 0) return { length: previewHeight, offset: 0, index };
|
|
565
|
+
if (index === 1) return { length: albumRowHeight, offset: previewHeight, index };
|
|
566
|
+
|
|
567
|
+
const offset = previewHeight + albumRowHeight + (index - 2) * rowHeight;
|
|
568
|
+
return { length: rowHeight, offset, index };
|
|
569
|
+
}}
|
|
570
|
+
removeClippedSubviews={false}
|
|
571
|
+
windowSize={11}
|
|
549
572
|
ListEmptyComponent={
|
|
550
573
|
<View style={styles.empty}>
|
|
551
574
|
<Text style={styles.emptyText}>{loading ? 'Loading…' : 'No media found'}</Text>
|
|
552
575
|
</View>
|
|
553
576
|
}
|
|
577
|
+
renderItem={({ item }) => {
|
|
578
|
+
if (item.type === 'preview') {
|
|
579
|
+
const previewStyleHeight = isRatioLocked && previewAspectRatio
|
|
580
|
+
? Dimensions.get('window').width / previewAspectRatio
|
|
581
|
+
: 420;
|
|
582
|
+
|
|
583
|
+
return (
|
|
584
|
+
<Animated.View
|
|
585
|
+
style={[
|
|
586
|
+
styles.preview,
|
|
587
|
+
{ transform: [{ scale: scaleAnim }], height: previewStyleHeight },
|
|
588
|
+
]}
|
|
589
|
+
>
|
|
590
|
+
{selectedMedia?.type === 'video' ? (
|
|
591
|
+
<Pressable style={styles.previewImage} onPress={() => setVideoPaused((v) => !v)}>
|
|
592
|
+
{playableUri && isActive ? (
|
|
593
|
+
<VideoPreview
|
|
594
|
+
uri={playableUri}
|
|
595
|
+
paused={videoPaused || !isActive}
|
|
596
|
+
muted={false}
|
|
597
|
+
style={styles.previewImage}
|
|
598
|
+
resizeMode="cover"
|
|
599
|
+
/>
|
|
600
|
+
) : (
|
|
601
|
+
<Image
|
|
602
|
+
source={{ uri: selectedMedia.thumbnailUri || selectedMedia.uri }}
|
|
603
|
+
style={styles.previewImage}
|
|
604
|
+
resizeMode="cover"
|
|
605
|
+
/>
|
|
606
|
+
)}
|
|
607
|
+
<View style={styles.previewOverlay}>
|
|
608
|
+
<View style={styles.playPauseCircle}>
|
|
609
|
+
<Ionicons name={videoPaused ? 'play' : 'pause'} size={24} color="#fff" />
|
|
610
|
+
</View>
|
|
611
|
+
</View>
|
|
612
|
+
</Pressable>
|
|
613
|
+
) : (
|
|
614
|
+
<Image
|
|
615
|
+
source={{ uri: previewUri ?? selectedMedia?.uri }}
|
|
616
|
+
style={[styles.previewImage, cropMode === '1:1' ? styles.squareCrop : styles.originalCrop]}
|
|
617
|
+
resizeMode={isRatioLocked ? 'cover' : (cropMode === '1:1' ? 'cover' : 'contain')}
|
|
618
|
+
/>
|
|
619
|
+
)}
|
|
620
|
+
|
|
621
|
+
<View style={styles.previewControls}>
|
|
622
|
+
{isRatioLocked && (
|
|
623
|
+
<View style={styles.ratioBadge}>
|
|
624
|
+
<Text style={styles.ratioBadgeText}>{aspectRatio}</Text>
|
|
625
|
+
</View>
|
|
626
|
+
)}
|
|
627
|
+
|
|
628
|
+
{!isRatioLocked && (
|
|
629
|
+
<Pressable
|
|
630
|
+
style={styles.cropToggle}
|
|
631
|
+
onPress={() => setCropMode((v) => (v === '1:1' ? 'original' : '1:1'))}
|
|
632
|
+
>
|
|
633
|
+
<Ionicons
|
|
634
|
+
name={cropMode === '1:1' ? 'square-outline' : 'expand-outline'}
|
|
635
|
+
size={20}
|
|
636
|
+
color="#fff"
|
|
637
|
+
/>
|
|
638
|
+
</Pressable>
|
|
639
|
+
)}
|
|
640
|
+
</View>
|
|
641
|
+
</Animated.View>
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (item.type === 'albumRow') {
|
|
646
|
+
return (
|
|
647
|
+
<View style={[styles.albumRow, { backgroundColor: '#0b0f1a', zIndex: 999, elevation: 5 }]}>
|
|
648
|
+
<Pressable
|
|
649
|
+
style={styles.albumSelector}
|
|
650
|
+
onPress={() => setShowAlbumPicker((v) => !v)}
|
|
651
|
+
>
|
|
652
|
+
<Text style={styles.albumTitle}>{activeAlbum.title}</Text>
|
|
653
|
+
<Ionicons name="chevron-down" size={16} color="#fff" style={{ marginLeft: 6 }} />
|
|
654
|
+
</Pressable>
|
|
655
|
+
|
|
656
|
+
{maxSelection > 1 && (
|
|
657
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
658
|
+
{multiSelect && (
|
|
659
|
+
<Text style={{ color: '#3b82f6', fontSize: 12, fontWeight: '700' }}>
|
|
660
|
+
{selectedItems.length}/{maxSelection}
|
|
661
|
+
</Text>
|
|
662
|
+
)}
|
|
663
|
+
<Pressable
|
|
664
|
+
style={[
|
|
665
|
+
styles.multiSelectBtn,
|
|
666
|
+
multiSelect && styles.multiSelectBtnActive,
|
|
667
|
+
multiSelect && selectedItems.length === 0 && { opacity: 0.35 },
|
|
668
|
+
]}
|
|
669
|
+
onPress={() => {
|
|
670
|
+
if (multiSelect) {
|
|
671
|
+
handleNext();
|
|
672
|
+
} else {
|
|
673
|
+
toggleMultiSelect();
|
|
674
|
+
}
|
|
675
|
+
}}
|
|
676
|
+
disabled={multiSelect && selectedItems.length === 0}
|
|
677
|
+
>
|
|
678
|
+
<Text style={[
|
|
679
|
+
styles.multiSelectText,
|
|
680
|
+
multiSelect && styles.multiSelectTextActive,
|
|
681
|
+
]}>{multiSelect ? 'Done' : 'Select multiple'}</Text>
|
|
682
|
+
</Pressable>
|
|
683
|
+
</View>
|
|
684
|
+
)}
|
|
685
|
+
</View>
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return (
|
|
690
|
+
<View style={{ flexDirection: 'row' }}>
|
|
691
|
+
{(item as any).items.map((mediaItem: any) => renderThumb({ item: mediaItem }))}
|
|
692
|
+
</View>
|
|
693
|
+
);
|
|
694
|
+
}}
|
|
554
695
|
/>
|
|
555
696
|
|
|
556
697
|
<View style={styles.tabBar}>
|
|
557
|
-
{
|
|
698
|
+
{mediaTabs.filter(t => mediaType === 'any' || (mediaType === 'photo' && t !== 'VIDEO') || (mediaType === 'video' && t !== 'PHOTO')).map((t) => (
|
|
558
699
|
<Pressable key={t} onPress={() => setTab(t)}>
|
|
559
700
|
<Text style={[styles.tab, tab === t && styles.tabActive]}>{t}</Text>
|
|
560
701
|
</Pressable>
|
|
@@ -794,12 +935,26 @@ const styles = StyleSheet.create({
|
|
|
794
935
|
justifyContent: 'center',
|
|
795
936
|
},
|
|
796
937
|
cropIcon: { color: '#fff', fontSize: 16 },
|
|
938
|
+
ratioBadge: {
|
|
939
|
+
backgroundColor: 'rgba(0,0,0,0.65)',
|
|
940
|
+
paddingHorizontal: 10,
|
|
941
|
+
paddingVertical: 5,
|
|
942
|
+
borderRadius: 12,
|
|
943
|
+
borderWidth: 1,
|
|
944
|
+
borderColor: 'rgba(255,255,255,0.3)',
|
|
945
|
+
},
|
|
946
|
+
ratioBadgeText: {
|
|
947
|
+
color: '#fff',
|
|
948
|
+
fontSize: 12,
|
|
949
|
+
fontWeight: '700',
|
|
950
|
+
letterSpacing: 0.5,
|
|
951
|
+
},
|
|
797
952
|
albumRow: {
|
|
798
953
|
flexDirection: 'row',
|
|
799
954
|
justifyContent: 'space-between',
|
|
800
955
|
alignItems: 'center',
|
|
801
956
|
paddingHorizontal: 16,
|
|
802
|
-
|
|
957
|
+
height: 48,
|
|
803
958
|
},
|
|
804
959
|
albumSelector: { flexDirection: 'row', alignItems: 'center' },
|
|
805
960
|
albumTitle: { fontSize: 16, fontWeight: '700', color: '#fff' },
|