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
@@ -1,31 +1,35 @@
1
1
  import React, { useState } from 'react';
2
- import { Alert, Dimensions, Image, Modal, Platform, Share, StyleSheet, Text, TouchableOpacity, View, } from 'react-native';
2
+ import { Alert, Image, Modal, Platform, StyleSheet, Text, TouchableOpacity, View, } from 'react-native';
3
3
  import { AppColors } from '../../styles/AppColors';
4
4
  import { AppFonts } from '../../styles/AppFonts';
5
5
  import TouchableScale from '../TouchableScale';
6
- import { CloseWhite, CopyIcon, FilmIcon, GifIcon, ImageIcon, SendIcon, TrashIcon, } from '../NetworkIcons';
7
- import { copyToClipboard, formatBytes } from '../../helpers';
6
+ import { ChevronDownIcon, ExpandCollapseIcon, FilmIcon, GifIcon, ImageIcon, PauseIcon, PlayIcon, RepeatIcon, TrashIcon, } from '../NetworkIcons';
7
+ import { ScreenCapture } from '../../capture';
8
+ import { formatBytes } from '../../helpers';
8
9
  import { showToast } from '../../helpers/toast';
9
10
  import { useTranslation } from '../../i18n';
10
11
  export const MediaPreviewModal = ({ item, visible, onClose, onDelete, onConvertToGif, }) => {
11
12
  const { t } = useTranslation();
12
13
  const [isConverting, setIsConverting] = useState(false);
13
14
  const [imageError, setImageError] = useState(false);
15
+ const [isExpanded, setIsExpanded] = useState(false);
16
+ const [isPlaying, setIsPlaying] = useState(false);
17
+ const [playbackSpeed, setPlaybackSpeed] = useState('1.0x');
18
+ const [isLooping, setIsLooping] = useState(true);
19
+ const [scrubPosition, setScrubPosition] = useState(0.4);
14
20
  if (!item)
15
21
  return null;
16
- const handleShare = async () => {
22
+ const handlePlayVideo = async () => {
17
23
  try {
18
- await Share.share(Platform.OS === 'ios'
19
- ? { url: item.uri, title: item.filename }
20
- : { message: `Captured media: ${item.filename}`, url: item.uri });
24
+ setIsPlaying(true);
25
+ await ScreenCapture.playVideo(item.uri);
21
26
  }
22
27
  catch {
23
- showToast(t('mediaGallery.shareUnavailable'));
28
+ showToast(t('mediaGallery.playError', 'Unable to play video'));
29
+ }
30
+ finally {
31
+ setIsPlaying(false);
24
32
  }
25
- };
26
- const handleCopyUri = () => {
27
- copyToClipboard(item.uri);
28
- showToast(t('mediaGallery.uriCopied'));
29
33
  };
30
34
  const handleDelete = () => {
31
35
  Alert.alert(t('mediaGallery.deleteTitle'), t('mediaGallery.deleteMessage', { filename: item.filename }), [
@@ -58,6 +62,10 @@ export const MediaPreviewModal = ({ item, visible, onClose, onDelete, onConvertT
58
62
  const isVideo = item.type === 'video';
59
63
  const isGif = item.type === 'gif';
60
64
  const durationSec = item.durationMs ? (item.durationMs / 1000).toFixed(1) : null;
65
+ const formattedDuration = durationSec
66
+ ? `00:${Number(durationSec) < 10 ? '0' : ''}${durationSec}`
67
+ : '00:05.0';
68
+ const hasBottomToolbar = isVideo || isGif;
61
69
  return (<Modal visible={visible} transparent={true} animationType="fade" onRequestClose={onClose}>
62
70
  <View style={previewStyles.overlay}>
63
71
 
@@ -101,15 +109,61 @@ export const MediaPreviewModal = ({ item, visible, onClose, onDelete, onConvertT
101
109
  </Text>
102
110
  </View>
103
111
  </View>
104
- <TouchableOpacity onPress={onClose} style={previewStyles.closeBtn} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
105
- <CloseWhite size={14} color={AppColors.white}/>
106
- </TouchableOpacity>
112
+
113
+
114
+ <View style={previewStyles.headerRight}>
115
+ <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={t('mediaGallery.delete')} onPress={handleDelete} style={previewStyles.headerDeleteBtn} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
116
+ <TrashIcon size={14} color={AppColors.red500}/>
117
+ </TouchableScale>
118
+
119
+ <TouchableOpacity accessible={true} accessibilityRole="button" accessibilityLabel={isExpanded ? t('mediaGallery.collapse', 'Exit Fullscreen') : t('mediaGallery.expand', 'Expand Fullscreen')} onPress={() => setIsExpanded(!isExpanded)} style={[
120
+ previewStyles.headerIconBtn,
121
+ isExpanded && { backgroundColor: `${AppColors.sky400}30`, borderColor: `${AppColors.sky400}70` },
122
+ ]} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
123
+ <ExpandCollapseIcon size={14} isExpanded={isExpanded} color={isExpanded ? AppColors.sky400 : AppColors.white}/>
124
+ </TouchableOpacity>
125
+
126
+ <TouchableOpacity accessible={true} accessibilityRole="button" accessibilityLabel={t('mediaGallery.minimize', 'Minimize')} onPress={onClose} style={previewStyles.headerIconBtn} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
127
+ <ChevronDownIcon size={15} color={AppColors.white}/>
128
+ </TouchableOpacity>
129
+ </View>
107
130
  </View>
108
131
 
109
132
 
110
- <View style={previewStyles.stage}>
111
- <View style={previewStyles.previewCard}>
112
- <Image source={{ uri: item.uri }} style={previewStyles.image} resizeMode="contain" onError={() => setImageError(true)}/>
133
+ <View style={[
134
+ previewStyles.stage,
135
+ isExpanded && previewStyles.stageExpanded,
136
+ !hasBottomToolbar && {
137
+ paddingBottom: Platform.OS === 'ios' ? 34 : 16,
138
+ },
139
+ ]}>
140
+ <TouchableOpacity activeOpacity={isVideo ? 0.9 : 1} onPress={isVideo ? handlePlayVideo : undefined} style={[
141
+ previewStyles.previewCard,
142
+ isExpanded && previewStyles.previewCardExpanded,
143
+ ]}>
144
+ {isVideo ? (item.thumbnailUri && !imageError ? (<>
145
+ <Image source={{ uri: item.thumbnailUri }} style={previewStyles.image} resizeMode="contain" onError={() => setImageError(true)}/>
146
+ <View style={previewStyles.videoImageOverlay}/>
147
+ </>) : (<View style={previewStyles.videoFallbackBackdrop}>
148
+ <View style={previewStyles.videoFallbackGlow}/>
149
+ <FilmIcon size={52} color={AppColors.sky400}/>
150
+ <Text style={previewStyles.videoFallbackTitle}>
151
+ {t('mediaGallery.playVideo', 'Play Video')}
152
+ </Text>
153
+ <Text style={previewStyles.videoFallbackSubtitle}>
154
+ {item.width && item.height ? `${item.width} × ${item.height}` : 'HD Video'} • MP4
155
+ </Text>
156
+ </View>)) : (<Image source={{ uri: item.uri }} style={previewStyles.image} resizeMode="contain" onError={() => setImageError(true)}/>)}
157
+
158
+
159
+ {isVideo && (<View style={previewStyles.centerPlayOverlay}>
160
+ <View style={previewStyles.centerPlayCircle}>
161
+ <PlayIcon size={30} color={AppColors.white}/>
162
+ </View>
163
+ <Text style={previewStyles.centerPlayText}>
164
+ {t('mediaGallery.playVideo', 'Play Video')}
165
+ </Text>
166
+ </View>)}
113
167
 
114
168
 
115
169
  {isVideo && (<View style={previewStyles.videoBadgeOverlay}>
@@ -128,67 +182,110 @@ export const MediaPreviewModal = ({ item, visible, onClose, onDelete, onConvertT
128
182
  <Text style={previewStyles.playPillText}>ANIMATED GIF</Text>
129
183
  </View>
130
184
  </View>)}
131
- </View>
132
185
 
133
-
134
- <View style={previewStyles.metaPillRow}>
135
- {item.width && item.height ? (<View style={previewStyles.metaPill}>
136
- <Text style={previewStyles.metaPillLabel}>DIMENSIONS</Text>
137
- <Text style={previewStyles.metaPillValue}>{`${item.width} × ${item.height}`}</Text>
138
- </View>) : null}
139
- <View style={previewStyles.metaPill}>
140
- <Text style={previewStyles.metaPillLabel}>SIZE</Text>
141
- <Text style={previewStyles.metaPillValue}>{formatBytes(item.sizeBytes)}</Text>
186
+
187
+ <View style={previewStyles.floatingMetaBadge}>
188
+ <Text style={previewStyles.floatingMetaText}>
189
+ {item.width && item.height ? `${item.width} × ${item.height} • ` : ''}
190
+ {formatBytes(item.sizeBytes)}
191
+ {durationSec ? ` • ${durationSec}s` : ''}
192
+ </Text>
142
193
  </View>
143
- {durationSec ? (<View style={previewStyles.metaPill}>
144
- <Text style={previewStyles.metaPillLabel}>DURATION</Text>
145
- <Text style={previewStyles.metaPillValue}>{`${durationSec}s`}</Text>
146
- </View>) : null}
147
- </View>
194
+ </TouchableOpacity>
148
195
  </View>
149
196
 
150
197
 
151
- <View style={previewStyles.footer}>
152
- {isVideo && onConvertToGif && (<TouchableScale onPress={handleConvert} disabled={isConverting} style={[
153
- previewStyles.actionBtn,
154
- {
155
- backgroundColor: `${AppColors.warningAmber}25`,
156
- borderColor: `${AppColors.warningAmber}70`,
157
- },
158
- ]}>
159
- <GifIcon size={15} color={AppColors.warningAmber}/>
160
- <Text style={[previewStyles.actionText, { color: AppColors.warningAmber }]}>
161
- {isConverting ? t('mediaGallery.converting') : t('mediaGallery.convertToGif')}
198
+ {isVideo && (<View style={previewStyles.videoControlsPanel}>
199
+
200
+ <View style={previewStyles.progressSection}>
201
+ <Text style={previewStyles.timeLabel}>
202
+ 00:0{Math.floor(scrubPosition * (Number(durationSec) || 5))}
162
203
  </Text>
163
- </TouchableScale>)}
204
+ <TouchableOpacity activeOpacity={0.8} onPress={() => setScrubPosition(pos => (pos >= 0.9 ? 0.1 : pos + 0.3))} style={previewStyles.progressTrack}>
205
+ <View style={[
206
+ previewStyles.progressFill,
207
+ { width: `${Math.max(10, Math.min(100, scrubPosition * 100))}%` },
208
+ ]}/>
209
+ <View style={[
210
+ previewStyles.scrubberThumb,
211
+ { left: `${Math.max(8, Math.min(96, scrubPosition * 100))}%` },
212
+ ]}/>
213
+ </TouchableOpacity>
214
+ <Text style={previewStyles.timeLabel}>{formattedDuration}</Text>
215
+ </View>
164
216
 
165
- <TouchableScale onPress={handleShare} style={previewStyles.actionBtn}>
166
- <SendIcon size={15} color={AppColors.sky400}/>
167
- <Text style={previewStyles.actionText}>{t('mediaGallery.share')}</Text>
168
- </TouchableScale>
217
+
218
+ <View style={previewStyles.controlsRow}>
219
+
220
+ <TouchableOpacity onPress={handlePlayVideo} style={previewStyles.controlActionPill}>
221
+ {isPlaying ? (<PauseIcon size={14} color={AppColors.sky400}/>) : (<PlayIcon size={14} color={AppColors.sky400}/>)}
222
+ <Text style={[previewStyles.controlActionText, { color: AppColors.sky400 }]}>
223
+ {isPlaying ? t('mediaGallery.pause', 'Pause') : t('mediaGallery.play', 'Play')}
224
+ </Text>
225
+ </TouchableOpacity>
169
226
 
170
- <TouchableScale onPress={handleCopyUri} style={previewStyles.actionBtn}>
171
- <CopyIcon size={15} color={AppColors.white}/>
172
- <Text style={previewStyles.actionText}>{t('mediaGallery.copyUri')}</Text>
173
- </TouchableScale>
227
+
228
+ <View style={previewStyles.speedGroup}>
229
+ {['1.0x', '1.5x', '2.0x'].map(speed => (<TouchableOpacity key={speed} onPress={() => setPlaybackSpeed(speed)} style={[
230
+ previewStyles.speedBtn,
231
+ playbackSpeed === speed && previewStyles.speedBtnActive,
232
+ ]}>
233
+ <Text style={[
234
+ previewStyles.speedText,
235
+ playbackSpeed === speed && previewStyles.speedTextActive,
236
+ ]}>
237
+ {speed}
238
+ </Text>
239
+ </TouchableOpacity>))}
240
+ </View>
174
241
 
175
- <TouchableScale onPress={handleDelete} style={[
176
- previewStyles.actionBtn,
177
- {
178
- backgroundColor: `${AppColors.red500}25`,
179
- borderColor: `${AppColors.red500}70`,
180
- },
181
- ]}>
182
- <TrashIcon size={15} color={AppColors.red500}/>
183
- <Text style={[previewStyles.actionText, { color: AppColors.red500 }]}>
184
- {t('mediaGallery.delete')}
185
- </Text>
186
- </TouchableScale>
187
- </View>
242
+
243
+ <TouchableOpacity onPress={() => setIsLooping(!isLooping)} style={[
244
+ previewStyles.loopPill,
245
+ isLooping && previewStyles.loopPillActive,
246
+ ]}>
247
+ <RepeatIcon size={12} color={isLooping ? AppColors.sky400 : AppColors.whiteAlpha60}/>
248
+ <Text style={[
249
+ previewStyles.loopText,
250
+ isLooping && previewStyles.loopTextActive,
251
+ ]}>
252
+ {isLooping ? 'Loop: ON' : 'Loop: OFF'}
253
+ </Text>
254
+ </TouchableOpacity>
255
+ </View>
256
+ </View>)}
257
+
258
+
259
+ {hasBottomToolbar && (<View style={previewStyles.footer}>
260
+ {isVideo && (<TouchableScale onPress={handlePlayVideo} style={[
261
+ previewStyles.actionBtn,
262
+ {
263
+ backgroundColor: `${AppColors.sky400}25`,
264
+ borderColor: `${AppColors.sky400}70`,
265
+ },
266
+ ]}>
267
+ <PlayIcon size={15} color={AppColors.sky400}/>
268
+ <Text style={[previewStyles.actionText, { color: AppColors.sky400, fontFamily: AppFonts.interSemiBold }]}>
269
+ {t('mediaGallery.playVideo', 'Play Video')}
270
+ </Text>
271
+ </TouchableScale>)}
272
+
273
+ {onConvertToGif && isVideo && (<TouchableScale onPress={handleConvert} disabled={isConverting} style={[
274
+ previewStyles.actionBtn,
275
+ {
276
+ backgroundColor: `${AppColors.warningAmber}25`,
277
+ borderColor: `${AppColors.warningAmber}70`,
278
+ },
279
+ ]}>
280
+ <GifIcon size={15} color={AppColors.warningAmber}/>
281
+ <Text style={[previewStyles.actionText, { color: AppColors.warningAmber }]}>
282
+ {isConverting ? t('mediaGallery.converting') : t('mediaGallery.convertToGif')}
283
+ </Text>
284
+ </TouchableScale>)}
285
+ </View>)}
188
286
  </View>
189
287
  </Modal>);
190
288
  };
191
- const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
192
289
  const previewStyles = StyleSheet.create({
193
290
  overlay: {
194
291
  flex: 1,
@@ -214,6 +311,11 @@ const previewStyles = StyleSheet.create({
214
311
  minWidth: 0,
215
312
  marginRight: 10,
216
313
  },
314
+ headerRight: {
315
+ flexDirection: 'row',
316
+ alignItems: 'center',
317
+ gap: 8,
318
+ },
217
319
  badge: {
218
320
  flexDirection: 'row',
219
321
  alignItems: 'center',
@@ -239,11 +341,23 @@ const previewStyles = StyleSheet.create({
239
341
  color: AppColors.whiteAlpha60,
240
342
  marginTop: 1,
241
343
  },
242
- closeBtn: {
344
+ headerDeleteBtn: {
345
+ width: 32,
346
+ height: 32,
347
+ borderRadius: 16,
348
+ backgroundColor: `${AppColors.red500}20`,
349
+ borderWidth: 1,
350
+ borderColor: `${AppColors.red500}60`,
351
+ alignItems: 'center',
352
+ justifyContent: 'center',
353
+ },
354
+ headerIconBtn: {
243
355
  width: 32,
244
356
  height: 32,
245
357
  borderRadius: 16,
246
358
  backgroundColor: AppColors.whiteAlpha12,
359
+ borderWidth: 1,
360
+ borderColor: AppColors.whiteAlpha20,
247
361
  alignItems: 'center',
248
362
  justifyContent: 'center',
249
363
  },
@@ -251,13 +365,19 @@ const previewStyles = StyleSheet.create({
251
365
  flex: 1,
252
366
  alignItems: 'center',
253
367
  justifyContent: 'center',
254
- paddingHorizontal: 16,
255
- paddingVertical: 12,
368
+ paddingHorizontal: 10,
369
+ paddingTop: 10,
370
+ paddingBottom: 10,
371
+ },
372
+ stageExpanded: {
373
+ paddingHorizontal: 0,
374
+ paddingTop: 0,
375
+ paddingBottom: 0,
256
376
  },
257
377
  previewCard: {
258
- width: SCREEN_WIDTH - 32,
259
- height: SCREEN_HEIGHT * 0.60,
260
- borderRadius: 12,
378
+ flex: 1,
379
+ width: '100%',
380
+ borderRadius: 14,
261
381
  overflow: 'hidden',
262
382
  backgroundColor: '#05070D',
263
383
  borderWidth: 1,
@@ -266,10 +386,73 @@ const previewStyles = StyleSheet.create({
266
386
  justifyContent: 'center',
267
387
  position: 'relative',
268
388
  },
389
+ previewCardExpanded: {
390
+ borderRadius: 0,
391
+ borderWidth: 0,
392
+ },
269
393
  image: {
270
394
  width: '100%',
271
395
  height: '100%',
272
396
  },
397
+ videoImageOverlay: {
398
+ ...StyleSheet.absoluteFillObject,
399
+ backgroundColor: 'rgba(5, 7, 13, 0.25)',
400
+ },
401
+ videoFallbackBackdrop: {
402
+ flex: 1,
403
+ width: '100%',
404
+ backgroundColor: '#070C18',
405
+ alignItems: 'center',
406
+ justifyContent: 'center',
407
+ gap: 8,
408
+ },
409
+ videoFallbackGlow: {
410
+ position: 'absolute',
411
+ width: 140,
412
+ height: 140,
413
+ borderRadius: 70,
414
+ backgroundColor: `${AppColors.sky400}18`,
415
+ },
416
+ videoFallbackTitle: {
417
+ fontFamily: AppFonts.interSemiBold,
418
+ fontSize: 15,
419
+ color: AppColors.white,
420
+ marginTop: 8,
421
+ },
422
+ videoFallbackSubtitle: {
423
+ fontFamily: AppFonts.interRegular,
424
+ fontSize: 12,
425
+ color: AppColors.whiteAlpha60,
426
+ },
427
+ centerPlayOverlay: {
428
+ position: 'absolute',
429
+ alignItems: 'center',
430
+ justifyContent: 'center',
431
+ gap: 8,
432
+ },
433
+ centerPlayCircle: {
434
+ width: 64,
435
+ height: 64,
436
+ borderRadius: 32,
437
+ backgroundColor: `${AppColors.sky400}E0`,
438
+ alignItems: 'center',
439
+ justifyContent: 'center',
440
+ paddingLeft: 3,
441
+ shadowColor: AppColors.sky400,
442
+ shadowOffset: { width: 0, height: 4 },
443
+ shadowOpacity: 0.5,
444
+ shadowRadius: 10,
445
+ elevation: 8,
446
+ },
447
+ centerPlayText: {
448
+ fontFamily: AppFonts.interSemiBold,
449
+ fontSize: 13,
450
+ color: AppColors.white,
451
+ letterSpacing: 0.3,
452
+ textShadowColor: 'rgba(0,0,0,0.7)',
453
+ textShadowOffset: { width: 0, height: 1 },
454
+ textShadowRadius: 3,
455
+ },
273
456
  videoBadgeOverlay: {
274
457
  position: 'absolute',
275
458
  bottom: 12,
@@ -292,34 +475,139 @@ const previewStyles = StyleSheet.create({
292
475
  color: AppColors.white,
293
476
  letterSpacing: 0.3,
294
477
  },
295
- metaPillRow: {
296
- flexDirection: 'row',
478
+ floatingMetaBadge: {
479
+ position: 'absolute',
480
+ bottom: 10,
481
+ backgroundColor: `${AppColors.slate900}D9`,
482
+ paddingHorizontal: 10,
483
+ paddingVertical: 4.5,
484
+ borderRadius: 8,
485
+ borderWidth: 1,
486
+ borderColor: AppColors.whiteAlpha15,
297
487
  alignItems: 'center',
298
488
  justifyContent: 'center',
489
+ },
490
+ floatingMetaText: {
491
+ fontFamily: AppFonts.interMedium,
492
+ fontSize: 10.5,
493
+ color: AppColors.slate200,
494
+ letterSpacing: 0.2,
495
+ },
496
+ videoControlsPanel: {
497
+ paddingHorizontal: 16,
498
+ paddingTop: 8,
499
+ paddingBottom: 6,
500
+ backgroundColor: AppColors.headerGlassDark,
501
+ borderTopWidth: 1,
502
+ borderTopColor: AppColors.borderGlassLight,
299
503
  gap: 8,
300
- marginTop: 14,
301
- flexWrap: 'wrap',
302
504
  },
303
- metaPill: {
304
- backgroundColor: AppColors.whiteAlpha08,
505
+ progressSection: {
506
+ flexDirection: 'row',
507
+ alignItems: 'center',
508
+ gap: 10,
509
+ },
510
+ timeLabel: {
511
+ fontFamily: AppFonts.interMedium,
512
+ fontSize: 11,
513
+ color: AppColors.whiteAlpha60,
514
+ minWidth: 42,
515
+ },
516
+ progressTrack: {
517
+ flex: 1,
518
+ height: 6,
519
+ borderRadius: 3,
520
+ backgroundColor: AppColors.whiteAlpha15,
521
+ justifyContent: 'center',
522
+ position: 'relative',
523
+ },
524
+ progressFill: {
525
+ height: '100%',
526
+ borderRadius: 3,
527
+ backgroundColor: AppColors.sky400,
528
+ },
529
+ scrubberThumb: {
530
+ position: 'absolute',
531
+ width: 12,
532
+ height: 12,
533
+ borderRadius: 6,
534
+ backgroundColor: AppColors.white,
535
+ borderWidth: 2,
536
+ borderColor: AppColors.sky400,
537
+ top: -3,
538
+ marginLeft: -6,
539
+ },
540
+ controlsRow: {
541
+ flexDirection: 'row',
542
+ alignItems: 'center',
543
+ justifyContent: 'space-between',
544
+ paddingVertical: 2,
545
+ },
546
+ controlActionPill: {
547
+ flexDirection: 'row',
548
+ alignItems: 'center',
549
+ gap: 5,
305
550
  paddingHorizontal: 10,
306
551
  paddingVertical: 5,
307
- borderRadius: 8,
552
+ borderRadius: 6,
553
+ backgroundColor: `${AppColors.sky400}18`,
554
+ borderWidth: 1,
555
+ borderColor: `${AppColors.sky400}50`,
556
+ },
557
+ controlActionText: {
558
+ fontFamily: AppFonts.interSemiBold,
559
+ fontSize: 11.5,
560
+ },
561
+ speedGroup: {
562
+ flexDirection: 'row',
563
+ alignItems: 'center',
564
+ backgroundColor: AppColors.whiteAlpha08,
565
+ borderRadius: 6,
308
566
  borderWidth: 1,
309
567
  borderColor: AppColors.whiteAlpha12,
568
+ padding: 2,
569
+ gap: 2,
570
+ },
571
+ speedBtn: {
572
+ paddingHorizontal: 7,
573
+ paddingVertical: 3,
574
+ borderRadius: 4,
575
+ },
576
+ speedBtnActive: {
577
+ backgroundColor: `${AppColors.sky400}30`,
578
+ },
579
+ speedText: {
580
+ fontFamily: AppFonts.interMedium,
581
+ fontSize: 10.5,
582
+ color: AppColors.whiteAlpha60,
583
+ },
584
+ speedTextActive: {
585
+ color: AppColors.sky400,
586
+ fontFamily: AppFonts.interBold,
587
+ },
588
+ loopPill: {
589
+ flexDirection: 'row',
310
590
  alignItems: 'center',
591
+ gap: 4,
592
+ paddingHorizontal: 8,
593
+ paddingVertical: 5,
594
+ borderRadius: 6,
595
+ backgroundColor: AppColors.whiteAlpha08,
596
+ borderWidth: 1,
597
+ borderColor: AppColors.whiteAlpha12,
598
+ },
599
+ loopPillActive: {
600
+ backgroundColor: `${AppColors.sky400}18`,
601
+ borderColor: `${AppColors.sky400}50`,
311
602
  },
312
- metaPillLabel: {
603
+ loopText: {
313
604
  fontFamily: AppFonts.interMedium,
314
- fontSize: 9,
315
- color: AppColors.slate400,
316
- letterSpacing: 0.4,
605
+ fontSize: 10.5,
606
+ color: AppColors.whiteAlpha60,
317
607
  },
318
- metaPillValue: {
608
+ loopTextActive: {
609
+ color: AppColors.sky400,
319
610
  fontFamily: AppFonts.interSemiBold,
320
- fontSize: 11.5,
321
- color: AppColors.white,
322
- marginTop: 1,
323
611
  },
324
612
  footer: {
325
613
  flexDirection: 'row',
@@ -328,8 +616,8 @@ const previewStyles = StyleSheet.create({
328
616
  flexWrap: 'wrap',
329
617
  gap: 8,
330
618
  paddingHorizontal: 16,
331
- paddingTop: 12,
332
- paddingBottom: Platform.OS === 'ios' ? 36 : 20,
619
+ paddingTop: 8,
620
+ paddingBottom: Platform.OS === 'ios' ? 34 : 16,
333
621
  borderTopWidth: 1,
334
622
  borderTopColor: AppColors.borderGlassLight,
335
623
  backgroundColor: AppColors.headerGlassDark,
@@ -338,7 +626,7 @@ const previewStyles = StyleSheet.create({
338
626
  flexDirection: 'row',
339
627
  alignItems: 'center',
340
628
  gap: 6,
341
- paddingHorizontal: 14,
629
+ paddingHorizontal: 16,
342
630
  paddingVertical: 9,
343
631
  borderRadius: 8,
344
632
  backgroundColor: AppColors.whiteAlpha08,