react-native-inapp-inspector 2.3.18 → 2.3.19

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 (79) hide show
  1. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +47 -0
  2. package/dist/commonjs/capture.d.ts +2 -0
  3. package/dist/commonjs/capture.js +3 -0
  4. package/dist/commonjs/components/EmptyState.d.ts +1 -0
  5. package/dist/commonjs/components/EmptyState.js +2 -2
  6. package/dist/commonjs/components/Inspector/FabLauncher.js +494 -39
  7. package/dist/commonjs/components/Inspector/InspectorHeader.js +45 -26
  8. package/dist/commonjs/components/Inspector/MainScreen.js +4 -3
  9. package/dist/commonjs/components/Inspector/MediaGalleryTab.js +418 -13
  10. package/dist/commonjs/components/Inspector/MediaPreviewModal.js +376 -88
  11. package/dist/commonjs/components/Inspector/NetworkDetail.js +49 -63
  12. package/dist/commonjs/components/Inspector/SettingsPanel.js +499 -119
  13. package/dist/commonjs/components/Inspector/TabBar.js +166 -4
  14. package/dist/commonjs/components/JsonViewer.js +4 -3
  15. package/dist/commonjs/components/NetworkIcons.d.ts +4 -0
  16. package/dist/commonjs/components/NetworkIcons.js +26 -1
  17. package/dist/commonjs/components/SegmentedTabs.js +1 -0
  18. package/dist/commonjs/constants/version.d.ts +1 -1
  19. package/dist/commonjs/constants/version.js +1 -1
  20. package/dist/commonjs/i18n/locales/en.json +20 -2
  21. package/dist/commonjs/index.js +45 -30
  22. package/dist/commonjs/native/NativeInspector.d.ts +3 -0
  23. package/dist/commonjs/native/NativeInspector.js +14 -1
  24. package/dist/commonjs/native/NativeNetworkInspector.d.ts +1 -0
  25. package/dist/commonjs/styles/AppColors.js +78 -78
  26. package/dist/commonjs/types/enums.d.ts +2 -0
  27. package/dist/commonjs/types/enums.js +2 -0
  28. package/dist/commonjs/types/interfaces.d.ts +5 -0
  29. package/dist/esm/capture.d.ts +2 -0
  30. package/dist/esm/capture.js +4 -1
  31. package/dist/esm/components/EmptyState.d.ts +1 -0
  32. package/dist/esm/components/EmptyState.js +2 -2
  33. package/dist/esm/components/Inspector/FabLauncher.js +462 -40
  34. package/dist/esm/components/Inspector/InspectorHeader.js +46 -27
  35. package/dist/esm/components/Inspector/MainScreen.js +4 -3
  36. package/dist/esm/components/Inspector/MediaGalleryTab.js +419 -14
  37. package/dist/esm/components/Inspector/MediaPreviewModal.js +379 -91
  38. package/dist/esm/components/Inspector/NetworkDetail.js +49 -63
  39. package/dist/esm/components/Inspector/SettingsPanel.js +500 -120
  40. package/dist/esm/components/Inspector/TabBar.js +135 -6
  41. package/dist/esm/components/JsonViewer.js +4 -3
  42. package/dist/esm/components/NetworkIcons.d.ts +4 -0
  43. package/dist/esm/components/NetworkIcons.js +21 -0
  44. package/dist/esm/components/SegmentedTabs.js +1 -0
  45. package/dist/esm/constants/version.d.ts +1 -1
  46. package/dist/esm/constants/version.js +1 -1
  47. package/dist/esm/i18n/locales/en.json +20 -2
  48. package/dist/esm/index.js +44 -29
  49. package/dist/esm/native/NativeInspector.d.ts +3 -0
  50. package/dist/esm/native/NativeInspector.js +12 -0
  51. package/dist/esm/native/NativeNetworkInspector.d.ts +1 -0
  52. package/dist/esm/styles/AppColors.js +78 -78
  53. package/dist/esm/types/enums.d.ts +2 -0
  54. package/dist/esm/types/enums.js +2 -0
  55. package/dist/esm/types/interfaces.d.ts +5 -0
  56. package/ios/NetworkInspectorModule.mm +241 -136
  57. package/package.json +1 -1
  58. package/react-native-inapp-inspector.podspec +1 -1
  59. package/src/capture.ts +8 -0
  60. package/src/components/EmptyState.tsx +3 -1
  61. package/src/components/Inspector/FabLauncher.tsx +562 -52
  62. package/src/components/Inspector/InspectorHeader.tsx +60 -22
  63. package/src/components/Inspector/MainScreen.tsx +3 -2
  64. package/src/components/Inspector/MediaGalleryTab.tsx +502 -28
  65. package/src/components/Inspector/MediaPreviewModal.tsx +465 -117
  66. package/src/components/Inspector/NetworkDetail.tsx +80 -97
  67. package/src/components/Inspector/SettingsPanel.tsx +708 -152
  68. package/src/components/Inspector/TabBar.tsx +174 -6
  69. package/src/components/JsonViewer.tsx +4 -2
  70. package/src/components/NetworkIcons.tsx +56 -0
  71. package/src/components/SegmentedTabs.tsx +1 -0
  72. package/src/constants/version.ts +1 -1
  73. package/src/i18n/locales/en.json +20 -4
  74. package/src/index.tsx +46 -31
  75. package/src/native/NativeInspector.ts +17 -0
  76. package/src/native/NativeNetworkInspector.ts +1 -0
  77. package/src/styles/AppColors.ts +80 -80
  78. package/src/types/enums.ts +2 -0
  79. package/src/types/interfaces.ts +5 -0
@@ -15,13 +15,15 @@ import {AppFonts} from '../../styles/AppFonts';
15
15
  import TouchableScale from '../TouchableScale';
16
16
  import EmptyState from '../EmptyState';
17
17
  import {
18
- CameraIcon,
18
+ CheckIcon,
19
19
  FilmIcon,
20
20
  GifIcon,
21
+ GridIcon,
21
22
  ImageIcon,
22
- SendIcon,
23
+ ListIcon,
24
+ PlayIcon,
25
+ ScreencastIcon,
23
26
  TrashIcon,
24
- VideoCameraIcon,
25
27
  } from '../NetworkIcons';
26
28
  import {MediaPreviewModal} from './MediaPreviewModal';
27
29
  import {
@@ -32,21 +34,27 @@ import {formatBytes} from '../../helpers';
32
34
  import {showToast} from '../../helpers/toast';
33
35
  import {useTranslation} from '../../i18n';
34
36
 
37
+ import {useInspector} from './InspectorContext';
38
+
35
39
  export const MediaGalleryTab: React.FC = () => {
36
40
  const {t} = useTranslation();
41
+ const {refreshMediaCount, switchActiveTab} = useInspector();
37
42
  const [mediaList, setMediaList] = useState<CapturedMediaItem[]>([]);
38
43
  const [refreshing, setRefreshing] = useState(false);
39
44
  const [selectedFilter, setSelectedFilter] = useState<'all' | 'image' | 'video' | 'gif'>('all');
40
45
  const [selectedItem, setSelectedItem] = useState<CapturedMediaItem | null>(null);
46
+ const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
47
+ const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
41
48
 
42
49
  const loadMedia = useCallback(async () => {
43
50
  try {
44
51
  const items = await ScreenCapture.getMediaList();
45
52
  setMediaList(items);
53
+ refreshMediaCount?.().catch(() => {});
46
54
  } catch {
47
55
  setMediaList([]);
48
56
  }
49
- }, []);
57
+ }, [refreshMediaCount]);
50
58
 
51
59
  useEffect(() => {
52
60
  loadMedia();
@@ -67,12 +75,63 @@ export const MediaGalleryTab: React.FC = () => {
67
75
  return mediaList.reduce((sum, item) => sum + (item.sizeBytes || 0), 0);
68
76
  }, [mediaList]);
69
77
 
78
+ const toggleSelect = useCallback((id: string) => {
79
+ setSelectedIds(prev => {
80
+ const next = new Set(prev);
81
+ if (next.has(id)) {
82
+ next.delete(id);
83
+ } else {
84
+ next.add(id);
85
+ }
86
+ return next;
87
+ });
88
+ }, []);
89
+
70
90
  const handleDeleteItem = async (item: CapturedMediaItem) => {
71
91
  await ScreenCapture.deleteMedia(item.uri);
92
+ setSelectedIds(prev => {
93
+ const next = new Set(prev);
94
+ next.delete(item.id);
95
+ return next;
96
+ });
72
97
  await loadMedia();
98
+ const count = await refreshMediaCount?.();
99
+ if (count === 0) {
100
+ switchActiveTab('apis');
101
+ }
73
102
  showToast(t('mediaGallery.deleted'));
74
103
  };
75
104
 
105
+ const handleDeleteSelected = () => {
106
+ const count = selectedIds.size;
107
+ if (count === 0) return;
108
+
109
+ Alert.alert(
110
+ t('mediaGallery.deleteSelectedTitle'),
111
+ t('mediaGallery.deleteSelectedMessage', {count}),
112
+ [
113
+ {text: t('common.cancel'), style: 'cancel'},
114
+ {
115
+ text: t('mediaGallery.delete'),
116
+ style: 'destructive',
117
+ onPress: async () => {
118
+ const itemsToDelete = mediaList.filter(item => selectedIds.has(item.id));
119
+ for (const item of itemsToDelete) {
120
+ await ScreenCapture.deleteMedia(item.uri);
121
+ }
122
+ setSelectedIds(new Set());
123
+ await loadMedia();
124
+ const remainingCount = await refreshMediaCount?.();
125
+ if (remainingCount === 0) {
126
+ switchActiveTab('apis');
127
+ }
128
+ showToast(t('mediaGallery.deletedCount', {count: itemsToDelete.length}));
129
+ },
130
+ },
131
+ ],
132
+ );
133
+ };
134
+
76
135
  const handleClearAll = () => {
77
136
  Alert.alert(
78
137
  t('mediaGallery.purgeTitle'),
@@ -84,7 +143,10 @@ export const MediaGalleryTab: React.FC = () => {
84
143
  style: 'destructive',
85
144
  onPress: async () => {
86
145
  await ScreenCapture.clearAllMedia();
146
+ setSelectedIds(new Set());
87
147
  await loadMedia();
148
+ await refreshMediaCount?.();
149
+ switchActiveTab('apis');
88
150
  showToast(t('mediaGallery.allPurged'));
89
151
  },
90
152
  },
@@ -96,23 +158,69 @@ export const MediaGalleryTab: React.FC = () => {
96
158
  const gifResult = await ScreenCapture.convertToGif(item.uri, {fps: 12, width: 480});
97
159
  if (gifResult) {
98
160
  await loadMedia();
161
+ refreshMediaCount?.().catch(() => {});
162
+ }
163
+ };
164
+
165
+ const renderFilterIcon = (filterKey: 'all' | 'image' | 'video' | 'gif', isActive: boolean) => {
166
+ const iconColor = isActive ? AppColors.sky400 : AppColors.slate400;
167
+ const iconSize = 12;
168
+
169
+ switch (filterKey) {
170
+ case 'all':
171
+ return <ScreencastIcon size={iconSize} color={iconColor} />;
172
+ case 'image':
173
+ return <ImageIcon size={iconSize} color={iconColor} />;
174
+ case 'video':
175
+ return <FilmIcon size={iconSize} color={iconColor} />;
176
+ case 'gif':
177
+ return <GifIcon size={iconSize} color={iconColor} />;
99
178
  }
100
179
  };
101
180
 
102
- const renderItem = ({item}: {item: CapturedMediaItem}) => {
181
+ const renderGridItem = ({item}: {item: CapturedMediaItem}) => {
103
182
  const isVideo = item.type === 'video';
104
183
  const isGif = item.type === 'gif';
184
+ const thumbUri = isVideo ? item.thumbnailUri : item.uri;
185
+ const isSelected = selectedIds.has(item.id);
105
186
 
106
187
  return (
107
188
  <TouchableScale
108
189
  onPress={() => setSelectedItem(item)}
109
- style={galleryStyles.card}>
190
+ style={[galleryStyles.card, isSelected && galleryStyles.cardSelected]}>
110
191
  <View style={galleryStyles.thumbnailContainer}>
111
- <Image
112
- source={{uri: item.uri}}
113
- style={galleryStyles.thumbnail}
114
- resizeMode="cover"
115
- />
192
+ {thumbUri ? (
193
+ <Image
194
+ source={{uri: thumbUri}}
195
+ style={galleryStyles.thumbnail}
196
+ resizeMode="cover"
197
+ />
198
+ ) : (
199
+ <View style={galleryStyles.thumbnailFallback}>
200
+ {isVideo ? (
201
+ <FilmIcon size={26} color={AppColors.sky400} />
202
+ ) : (
203
+ <ImageIcon size={26} color={AppColors.slate400} />
204
+ )}
205
+ </View>
206
+ )}
207
+
208
+ {/* Checkbox top-left */}
209
+ <TouchableOpacity
210
+ onPress={() => toggleSelect(item.id)}
211
+ style={[
212
+ galleryStyles.checkbox,
213
+ isSelected && galleryStyles.checkboxSelected,
214
+ ]}
215
+ hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
216
+ {isSelected && <CheckIcon size={12} color={AppColors.white} />}
217
+ </TouchableOpacity>
218
+
219
+ {isVideo && (
220
+ <View style={galleryStyles.miniPlayCircle}>
221
+ <PlayIcon size={14} color={AppColors.white} />
222
+ </View>
223
+ )}
116
224
  <View style={galleryStyles.typeBadge}>
117
225
  {isVideo ? (
118
226
  <FilmIcon size={11} color={AppColors.sky400} />
@@ -145,9 +253,94 @@ export const MediaGalleryTab: React.FC = () => {
145
253
  );
146
254
  };
147
255
 
256
+ const renderListItem = ({item}: {item: CapturedMediaItem}) => {
257
+ const isVideo = item.type === 'video';
258
+ const isGif = item.type === 'gif';
259
+ const thumbUri = isVideo ? item.thumbnailUri : item.uri;
260
+ const isSelected = selectedIds.has(item.id);
261
+
262
+ return (
263
+ <TouchableScale
264
+ onPress={() => setSelectedItem(item)}
265
+ style={[galleryStyles.listRow, isSelected && galleryStyles.listRowSelected]}>
266
+ {/* Checkbox */}
267
+ <TouchableOpacity
268
+ onPress={() => toggleSelect(item.id)}
269
+ style={[
270
+ galleryStyles.listCheckbox,
271
+ isSelected && galleryStyles.checkboxSelected,
272
+ ]}
273
+ hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
274
+ {isSelected && <CheckIcon size={12} color={AppColors.white} />}
275
+ </TouchableOpacity>
276
+
277
+ {/* Square Thumbnail */}
278
+ <View style={galleryStyles.listThumbContainer}>
279
+ {thumbUri ? (
280
+ <Image
281
+ source={{uri: thumbUri}}
282
+ style={galleryStyles.listThumb}
283
+ resizeMode="cover"
284
+ />
285
+ ) : (
286
+ <View style={galleryStyles.listThumbFallback}>
287
+ {isVideo ? (
288
+ <FilmIcon size={18} color={AppColors.sky400} />
289
+ ) : (
290
+ <ImageIcon size={18} color={AppColors.slate400} />
291
+ )}
292
+ </View>
293
+ )}
294
+ {isVideo && (
295
+ <View style={galleryStyles.listMiniPlay}>
296
+ <PlayIcon size={10} color={AppColors.white} />
297
+ </View>
298
+ )}
299
+ </View>
300
+
301
+ {/* Text Metadata */}
302
+ <View style={galleryStyles.listInfo}>
303
+ <Text style={galleryStyles.listTitle} numberOfLines={1}>
304
+ {item.filename}
305
+ </Text>
306
+ <View style={galleryStyles.listMetaRow}>
307
+ <View style={galleryStyles.listBadge}>
308
+ {isVideo ? (
309
+ <FilmIcon size={10} color={AppColors.sky400} />
310
+ ) : isGif ? (
311
+ <GifIcon size={10} color={AppColors.warningAmber} />
312
+ ) : (
313
+ <ImageIcon size={10} color={AppColors.emerald500} />
314
+ )}
315
+ <Text style={galleryStyles.listBadgeText}>
316
+ {item.format.toUpperCase()}
317
+ </Text>
318
+ </View>
319
+ <Text style={galleryStyles.cardMeta}>
320
+ {formatBytes(item.sizeBytes)}
321
+ </Text>
322
+ <Text style={galleryStyles.cardMetaDot}>•</Text>
323
+ <Text style={galleryStyles.cardMeta}>
324
+ {new Date(item.timestamp).toLocaleDateString([], {month: 'short', day: 'numeric'})}{' '}
325
+ {new Date(item.timestamp).toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'})}
326
+ </Text>
327
+ </View>
328
+ </View>
329
+
330
+ {/* Delete button */}
331
+ <TouchableOpacity
332
+ onPress={() => handleDeleteItem(item)}
333
+ style={galleryStyles.listDeleteBtn}
334
+ hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
335
+ <TrashIcon size={14} color={AppColors.red500} />
336
+ </TouchableOpacity>
337
+ </TouchableScale>
338
+ );
339
+ };
340
+
148
341
  return (
149
342
  <View style={galleryStyles.container}>
150
- {/* Top Bar with Filter Chips and Clear All */}
343
+ {/* Top Bar with Filter Chips on Left & Grid/List Toggle on Right */}
151
344
  <View style={galleryStyles.filterBar}>
152
345
  <View style={galleryStyles.chipGroup}>
153
346
  {(['all', 'image', 'video', 'gif'] as const).map(filterKey => {
@@ -169,6 +362,7 @@ export const MediaGalleryTab: React.FC = () => {
169
362
  galleryStyles.chip,
170
363
  isActive && galleryStyles.chipActive,
171
364
  ]}>
365
+ {renderFilterIcon(filterKey, isActive)}
172
366
  <Text
173
367
  style={[
174
368
  galleryStyles.chipText,
@@ -181,24 +375,89 @@ export const MediaGalleryTab: React.FC = () => {
181
375
  })}
182
376
  </View>
183
377
 
184
- {mediaList.length > 0 && (
378
+ {/* View Mode Toggle Button Group */}
379
+ <View style={galleryStyles.viewToggleContainer}>
185
380
  <TouchableOpacity
186
- onPress={handleClearAll}
187
- style={galleryStyles.clearBtn}
188
- hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
189
- <TrashIcon size={13} color={AppColors.red500} />
190
- <Text style={galleryStyles.clearBtnText}>{t('mediaGallery.purge', {size: formatBytes(totalStorageBytes)})}</Text>
381
+ onPress={() => setViewMode('grid')}
382
+ style={[
383
+ galleryStyles.viewToggleBtn,
384
+ viewMode === 'grid' && galleryStyles.viewToggleBtnActive,
385
+ ]}>
386
+ <GridIcon size={13} color={viewMode === 'grid' ? AppColors.white : AppColors.slate400} />
387
+ </TouchableOpacity>
388
+ <TouchableOpacity
389
+ onPress={() => setViewMode('list')}
390
+ style={[
391
+ galleryStyles.viewToggleBtn,
392
+ viewMode === 'list' && galleryStyles.viewToggleBtnActive,
393
+ ]}>
394
+ <ListIcon size={13} color={viewMode === 'list' ? AppColors.white : AppColors.slate400} />
191
395
  </TouchableOpacity>
192
- )}
396
+ </View>
193
397
  </View>
194
398
 
195
- {/* Grid of Media Items */}
399
+ {/* Sub Bar with Results Counter on Left & Actions on Right */}
400
+ {mediaList.length > 0 && (
401
+ <View style={galleryStyles.subBar}>
402
+ <View style={galleryStyles.resultCountWrapper}>
403
+ <Text
404
+ style={[
405
+ galleryStyles.resultCountText,
406
+ selectedIds.size > 0 && galleryStyles.resultCountTextSelected,
407
+ ]}>
408
+ {selectedIds.size > 0
409
+ ? t('mediaGallery.selectedOfResults', {
410
+ selected: selectedIds.size,
411
+ total: filteredMedia.length,
412
+ defaultValue: `Selected ${selectedIds.size} of ${filteredMedia.length} results`,
413
+ })
414
+ : filteredMedia.length !== mediaList.length
415
+ ? t('mediaGallery.showingFilteredResults', {
416
+ count: filteredMedia.length,
417
+ total: mediaList.length,
418
+ defaultValue: `Showing ${filteredMedia.length} of ${mediaList.length} results`,
419
+ })
420
+ : t('mediaGallery.showingResults', {
421
+ count: filteredMedia.length,
422
+ defaultValue: `Showing ${filteredMedia.length} results`,
423
+ })}
424
+ </Text>
425
+ </View>
426
+
427
+ <View style={galleryStyles.actionsGroup}>
428
+ {selectedIds.size > 0 && (
429
+ <TouchableOpacity
430
+ onPress={handleDeleteSelected}
431
+ style={galleryStyles.deleteBtn}
432
+ hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
433
+ <TrashIcon size={13} color={AppColors.red500} />
434
+ <Text style={galleryStyles.deleteBtnText}>
435
+ {t('mediaGallery.deleteSelected', {count: selectedIds.size})}
436
+ </Text>
437
+ </TouchableOpacity>
438
+ )}
439
+
440
+ <TouchableOpacity
441
+ onPress={handleClearAll}
442
+ style={galleryStyles.clearBtn}
443
+ hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
444
+ <TrashIcon size={13} color={AppColors.red500} />
445
+ <Text style={galleryStyles.clearBtnText}>
446
+ {t('mediaGallery.purge', {size: formatBytes(totalStorageBytes)})}
447
+ </Text>
448
+ </TouchableOpacity>
449
+ </View>
450
+ </View>
451
+ )}
452
+
453
+ {/* Media Items (Grid or List) */}
196
454
  <FlatList
455
+ key={viewMode}
197
456
  data={filteredMedia}
198
457
  keyExtractor={item => item.id}
199
- renderItem={renderItem}
200
- numColumns={2}
201
- columnWrapperStyle={galleryStyles.columnWrapper}
458
+ renderItem={viewMode === 'grid' ? renderGridItem : renderListItem}
459
+ numColumns={viewMode === 'grid' ? 2 : 1}
460
+ columnWrapperStyle={viewMode === 'grid' ? galleryStyles.columnWrapper : undefined}
202
461
  contentContainerStyle={galleryStyles.listContent}
203
462
  refreshControl={
204
463
  <RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={AppColors.sky400} />
@@ -207,11 +466,11 @@ export const MediaGalleryTab: React.FC = () => {
207
466
  <EmptyState
208
467
  customTitle={t('mediaGallery.noMediaTitle')}
209
468
  customSub={t('mediaGallery.noMediaDesc')}
469
+ showReload={false}
210
470
  />
211
471
  }
212
472
  />
213
473
 
214
-
215
474
  {/* Fullscreen Preview Modal */}
216
475
  <MediaPreviewModal
217
476
  item={selectedItem}
@@ -237,19 +496,22 @@ const galleryStyles = StyleSheet.create({
237
496
  alignItems: 'center',
238
497
  justifyContent: 'space-between',
239
498
  paddingHorizontal: 12,
240
- paddingVertical: 10,
499
+ paddingVertical: 9,
241
500
  borderBottomWidth: 1,
242
501
  borderBottomColor: AppColors.borderGlassLight,
243
- flexWrap: 'wrap',
244
502
  gap: 8,
245
503
  },
246
504
  chipGroup: {
247
505
  flexDirection: 'row',
248
506
  alignItems: 'center',
249
507
  gap: 6,
508
+ flexShrink: 1,
250
509
  },
251
510
  chip: {
252
- paddingHorizontal: 9,
511
+ flexDirection: 'row',
512
+ alignItems: 'center',
513
+ gap: 4.5,
514
+ paddingHorizontal: 8,
253
515
  paddingVertical: 4.5,
254
516
  borderRadius: 6,
255
517
  backgroundColor: AppColors.whiteAlpha06,
@@ -269,6 +531,71 @@ const galleryStyles = StyleSheet.create({
269
531
  fontFamily: AppFonts.interBold,
270
532
  color: AppColors.sky400,
271
533
  },
534
+ subBar: {
535
+ flexDirection: 'row',
536
+ alignItems: 'center',
537
+ justifyContent: 'space-between',
538
+ paddingHorizontal: 12,
539
+ paddingVertical: 7,
540
+ backgroundColor: `${AppColors.slate900}99`,
541
+ borderBottomWidth: 1,
542
+ borderBottomColor: AppColors.borderGlassLight,
543
+ gap: 8,
544
+ },
545
+ resultCountWrapper: {
546
+ flexDirection: 'row',
547
+ alignItems: 'center',
548
+ flexShrink: 1,
549
+ },
550
+ resultCountText: {
551
+ fontFamily: AppFonts.interMedium,
552
+ fontSize: 11,
553
+ color: AppColors.slate400,
554
+ },
555
+ resultCountTextSelected: {
556
+ fontFamily: AppFonts.interSemiBold,
557
+ color: AppColors.sky400,
558
+ },
559
+ actionsGroup: {
560
+ flexDirection: 'row',
561
+ alignItems: 'center',
562
+ gap: 6,
563
+ },
564
+ viewToggleContainer: {
565
+ flexDirection: 'row',
566
+ alignItems: 'center',
567
+ backgroundColor: AppColors.whiteAlpha06,
568
+ borderRadius: 6,
569
+ padding: 2,
570
+ borderWidth: 1,
571
+ borderColor: AppColors.whiteAlpha10,
572
+ },
573
+ viewToggleBtn: {
574
+ paddingHorizontal: 7,
575
+ paddingVertical: 4,
576
+ borderRadius: 4,
577
+ alignItems: 'center',
578
+ justifyContent: 'center',
579
+ },
580
+ viewToggleBtnActive: {
581
+ backgroundColor: `${AppColors.violet600}80`,
582
+ },
583
+ deleteBtn: {
584
+ flexDirection: 'row',
585
+ alignItems: 'center',
586
+ gap: 4,
587
+ paddingHorizontal: 8,
588
+ paddingVertical: 4,
589
+ borderRadius: 6,
590
+ backgroundColor: `${AppColors.red500}26`,
591
+ borderWidth: 1,
592
+ borderColor: `${AppColors.red500}80`,
593
+ },
594
+ deleteBtnText: {
595
+ fontFamily: AppFonts.interBold,
596
+ fontSize: 10.5,
597
+ color: AppColors.red500,
598
+ },
272
599
  clearBtn: {
273
600
  flexDirection: 'row',
274
601
  alignItems: 'center',
@@ -287,7 +614,7 @@ const galleryStyles = StyleSheet.create({
287
614
  },
288
615
  listContent: {
289
616
  padding: 12,
290
- gap: 12,
617
+ gap: 10,
291
618
  },
292
619
  columnWrapper: {
293
620
  gap: 12,
@@ -300,6 +627,10 @@ const galleryStyles = StyleSheet.create({
300
627
  borderColor: AppColors.borderGlassLight,
301
628
  overflow: 'hidden',
302
629
  },
630
+ cardSelected: {
631
+ borderColor: AppColors.violet600,
632
+ borderWidth: 1.5,
633
+ },
303
634
  thumbnailContainer: {
304
635
  width: '100%',
305
636
  height: 110,
@@ -310,6 +641,50 @@ const galleryStyles = StyleSheet.create({
310
641
  width: '100%',
311
642
  height: '100%',
312
643
  },
644
+ thumbnailFallback: {
645
+ width: '100%',
646
+ height: '100%',
647
+ alignItems: 'center',
648
+ justifyContent: 'center',
649
+ backgroundColor: AppColors.slate900,
650
+ },
651
+ checkbox: {
652
+ position: 'absolute',
653
+ top: 6,
654
+ left: 6,
655
+ width: 22,
656
+ height: 22,
657
+ borderRadius: 5,
658
+ borderWidth: 1.5,
659
+ borderColor: 'rgba(255, 255, 255, 0.65)',
660
+ backgroundColor: 'rgba(15, 23, 42, 0.65)',
661
+ alignItems: 'center',
662
+ justifyContent: 'center',
663
+ zIndex: 10,
664
+ },
665
+ checkboxSelected: {
666
+ backgroundColor: AppColors.violet600,
667
+ borderColor: AppColors.violet600,
668
+ },
669
+ miniPlayCircle: {
670
+ position: 'absolute',
671
+ top: '50%',
672
+ left: '50%',
673
+ width: 32,
674
+ height: 32,
675
+ marginTop: -16,
676
+ marginLeft: -16,
677
+ borderRadius: 16,
678
+ backgroundColor: `${AppColors.sky400}D9`,
679
+ alignItems: 'center',
680
+ justifyContent: 'center',
681
+ paddingLeft: 2,
682
+ shadowColor: '#000',
683
+ shadowOffset: {width: 0, height: 2},
684
+ shadowOpacity: 0.4,
685
+ shadowRadius: 4,
686
+ elevation: 4,
687
+ },
313
688
  typeBadge: {
314
689
  position: 'absolute',
315
690
  top: 6,
@@ -352,4 +727,103 @@ const galleryStyles = StyleSheet.create({
352
727
  fontSize: 10,
353
728
  color: AppColors.whiteAlpha30,
354
729
  },
730
+ /* List View Styles */
731
+ listRow: {
732
+ flexDirection: 'row',
733
+ alignItems: 'center',
734
+ backgroundColor: AppColors.slate800,
735
+ borderRadius: 8,
736
+ borderWidth: 1,
737
+ borderColor: AppColors.borderGlassLight,
738
+ padding: 8,
739
+ gap: 10,
740
+ },
741
+ listRowSelected: {
742
+ borderColor: AppColors.violet600,
743
+ borderWidth: 1.5,
744
+ backgroundColor: `${AppColors.violet600}14`,
745
+ },
746
+ listCheckbox: {
747
+ width: 20,
748
+ height: 20,
749
+ borderRadius: 4,
750
+ borderWidth: 1.5,
751
+ borderColor: 'rgba(255, 255, 255, 0.65)',
752
+ backgroundColor: 'rgba(15, 23, 42, 0.65)',
753
+ alignItems: 'center',
754
+ justifyContent: 'center',
755
+ },
756
+ listThumbContainer: {
757
+ width: 46,
758
+ height: 46,
759
+ borderRadius: 6,
760
+ backgroundColor: AppColors.slate850,
761
+ overflow: 'hidden',
762
+ position: 'relative',
763
+ },
764
+ listThumb: {
765
+ width: '100%',
766
+ height: '100%',
767
+ },
768
+ listThumbFallback: {
769
+ width: '100%',
770
+ height: '100%',
771
+ alignItems: 'center',
772
+ justifyContent: 'center',
773
+ backgroundColor: AppColors.slate900,
774
+ },
775
+ listMiniPlay: {
776
+ position: 'absolute',
777
+ top: '50%',
778
+ left: '50%',
779
+ width: 20,
780
+ height: 20,
781
+ marginTop: -10,
782
+ marginLeft: -10,
783
+ borderRadius: 10,
784
+ backgroundColor: `${AppColors.sky400}D9`,
785
+ alignItems: 'center',
786
+ justifyContent: 'center',
787
+ paddingLeft: 1,
788
+ },
789
+ listInfo: {
790
+ flex: 1,
791
+ gap: 4,
792
+ minWidth: 0,
793
+ },
794
+ listTitle: {
795
+ fontFamily: AppFonts.interMedium,
796
+ fontSize: 12,
797
+ color: AppColors.white,
798
+ },
799
+ listMetaRow: {
800
+ flexDirection: 'row',
801
+ alignItems: 'center',
802
+ gap: 6,
803
+ flexWrap: 'wrap',
804
+ },
805
+ listBadge: {
806
+ flexDirection: 'row',
807
+ alignItems: 'center',
808
+ gap: 3,
809
+ backgroundColor: AppColors.badgeGlassDark,
810
+ paddingHorizontal: 4,
811
+ paddingVertical: 1.5,
812
+ borderRadius: 4,
813
+ borderWidth: 1,
814
+ borderColor: AppColors.borderGlassMedium,
815
+ },
816
+ listBadgeText: {
817
+ fontFamily: AppFonts.interBold,
818
+ fontSize: 8.5,
819
+ color: AppColors.slate200,
820
+ },
821
+ listDeleteBtn: {
822
+ padding: 6,
823
+ borderRadius: 6,
824
+ backgroundColor: `${AppColors.red500}14`,
825
+ borderWidth: 1,
826
+ borderColor: `${AppColors.red500}33`,
827
+ },
355
828
  });
829
+