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,49 +1,471 @@
1
- import React from 'react';
2
- import { Animated, View } from 'react-native';
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { Animated, PanResponder, Platform, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View, } from 'react-native';
3
3
  import LinearGradient from 'react-native-linear-gradient';
4
4
  import { useInspector } from './InspectorContext';
5
5
  import TouchableScale from '../TouchableScale';
6
+ import AppHeaderLogo from '../AppHeaderLogo';
6
7
  import BrandCircleIcon from '../BrandCircleIcon';
8
+ import { AndroidIcon, AppleIcon, CameraIcon, NpmIcon, VideoCameraIcon, } from '../NetworkIcons';
9
+ import { ScreenCapture } from '../../capture';
10
+ import { triggerNativeHaptic } from '../../native/NativeInspector';
11
+ import { showToast } from '../../helpers/toast';
12
+ import { getAppName, getAppVersionAndBuild } from '../../helpers';
13
+ import { LIB_VERSION } from '../../constants';
7
14
  import { AppColors } from '../../styles/AppColors';
15
+ import { AppFonts } from '../../styles/AppFonts';
8
16
  import styles from '../../styles';
17
+ import { useTranslation } from '../../i18n';
18
+ import { MediaPreviewModal } from './MediaPreviewModal';
9
19
  const FabLauncher = () => {
10
- const { setVisible, fabPan, fabPanResponder, fabDraggedRef, pulseAnim, fabShineAnim, unreadPulseAnim, logs, analyticsEvents, } = useInspector();
11
- return (<Animated.View style={[styles.fabWrapper, { transform: fabPan.getTranslateTransform() }]} {...fabPanResponder.panHandlers}>
12
- <TouchableScale style={{ alignItems: 'center', justifyContent: 'center' }} onPress={() => {
13
- if (fabDraggedRef.current)
14
- return;
15
- setVisible(true);
16
- }} hitSlop={10}>
17
- <Animated.View style={[styles.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
18
- <BrandCircleIcon size={62}/>
19
-
20
- <View pointerEvents="none" style={styles.fabShineClip}>
21
- <Animated.View style={[
22
- styles.fabShineStreak,
23
- {
24
- transform: [
25
- {
26
- translateX: fabShineAnim.interpolate({
27
- inputRange: [0, 1],
28
- outputRange: [-48, 96],
29
- }),
30
- },
31
- { rotate: '25deg' },
32
- ],
33
- },
34
- ]}>
35
- <LinearGradient colors={[
36
- `${AppColors.white}00`,
37
- `${AppColors.white}8C`,
38
- `${AppColors.white}00`,
39
- ]} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} style={{ flex: 1 }}/>
40
- </Animated.View>
41
- </View>
42
- {(logs.length > 0 || analyticsEvents.length > 0) && (<Animated.View style={[
43
- styles.fabGreenDot,
44
- { transform: [{ scale: unreadPulseAnim }] },
45
- ]}/>)}
46
- </TouchableScale>
47
- </Animated.View>);
20
+ const { setVisible, isMinimized, setIsMinimized, appIcon, fabPan, fabPanResponder, fabDraggedRef, pulseAnim, fabShineAnim, unreadPulseAnim, logs, analyticsEvents, refreshMediaCount, } = useInspector();
21
+ const { width: screenWidth } = useWindowDimensions();
22
+ const { t } = useTranslation();
23
+ const [appVersionString] = useState(() => {
24
+ return getAppVersionAndBuild().formatted;
25
+ });
26
+ const [isRecording, setIsRecording] = useState(false);
27
+ const [recordingSeconds, setRecordingSeconds] = useState(0);
28
+ const recordingTimerRef = useRef(null);
29
+ const [previewMediaItem, setPreviewMediaItem] = useState(null);
30
+ const playerPan = useRef(new Animated.ValueXY({ x: 0, y: 0 })).current;
31
+ const playerPanRef = useRef({ x: 0, y: 0 });
32
+ useEffect(() => {
33
+ const idX = playerPan.x.addListener(v => (playerPanRef.current.x = v.value));
34
+ const idY = playerPan.y.addListener(v => (playerPanRef.current.y = v.value));
35
+ return () => {
36
+ playerPan.x.removeListener(idX);
37
+ playerPan.y.removeListener(idY);
38
+ };
39
+ }, [playerPan]);
40
+ const playerDraggedRef = useRef(false);
41
+ const playerPanResponder = useRef(PanResponder.create({
42
+ onStartShouldSetPanResponder: () => false,
43
+ onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 4 || Math.abs(g.dy) > 4,
44
+ onPanResponderGrant: () => {
45
+ playerDraggedRef.current = true;
46
+ playerPan.setOffset({
47
+ x: playerPanRef.current.x,
48
+ y: playerPanRef.current.y,
49
+ });
50
+ playerPan.setValue({ x: 0, y: 0 });
51
+ },
52
+ onPanResponderMove: Animated.event([null, { dx: playerPan.x, dy: playerPan.y }], { useNativeDriver: false }),
53
+ onPanResponderRelease: () => {
54
+ playerPan.flattenOffset();
55
+ setTimeout(() => {
56
+ playerDraggedRef.current = false;
57
+ }, 50);
58
+ },
59
+ onPanResponderTerminate: () => {
60
+ playerPan.flattenOffset();
61
+ playerDraggedRef.current = false;
62
+ },
63
+ })).current;
64
+ useEffect(() => {
65
+ ScreenCapture.isRecording()
66
+ .then(active => {
67
+ setIsRecording(Boolean(active));
68
+ })
69
+ .catch(() => { });
70
+ }, []);
71
+ useEffect(() => {
72
+ if (isRecording) {
73
+ setRecordingSeconds(0);
74
+ recordingTimerRef.current = setInterval(() => {
75
+ setRecordingSeconds(prev => prev + 1);
76
+ }, 1000);
77
+ }
78
+ else {
79
+ if (recordingTimerRef.current) {
80
+ clearInterval(recordingTimerRef.current);
81
+ recordingTimerRef.current = null;
82
+ }
83
+ setRecordingSeconds(0);
84
+ }
85
+ return () => {
86
+ if (recordingTimerRef.current) {
87
+ clearInterval(recordingTimerRef.current);
88
+ }
89
+ };
90
+ }, [isRecording]);
91
+ const handleTakeScreenshot = useCallback(async () => {
92
+ if (playerDraggedRef.current)
93
+ return;
94
+ try {
95
+ triggerNativeHaptic('light');
96
+ const result = await ScreenCapture.takeScreenshot({
97
+ format: 'png',
98
+ quality: 0.9,
99
+ hideInspector: true,
100
+ });
101
+ if (result) {
102
+ triggerNativeHaptic('success');
103
+ const newItem = {
104
+ id: `screenshot_${result.timestamp}.png`,
105
+ type: 'image',
106
+ format: result.format,
107
+ uri: result.uri,
108
+ filename: result.uri.split('/').pop() || `screenshot_${result.timestamp}.png`,
109
+ sizeBytes: result.sizeBytes,
110
+ timestamp: result.timestamp,
111
+ width: result.width,
112
+ height: result.height,
113
+ };
114
+ setPreviewMediaItem(newItem);
115
+ refreshMediaCount?.().catch(() => { });
116
+ showToast(t('header.screenshotCaptured', 'Screenshot captured'));
117
+ }
118
+ else {
119
+ showToast(t('header.screenshotFailed', 'Failed to capture screenshot'));
120
+ }
121
+ }
122
+ catch {
123
+ showToast(t('header.screenshotError', 'Error capturing screenshot'));
124
+ }
125
+ }, [refreshMediaCount, t]);
126
+ const handleToggleVideoRecording = useCallback(async () => {
127
+ if (playerDraggedRef.current)
128
+ return;
129
+ try {
130
+ if (isRecording) {
131
+ triggerNativeHaptic('medium');
132
+ const result = await ScreenCapture.stopRecording();
133
+ setIsRecording(false);
134
+ if (result) {
135
+ triggerNativeHaptic('success');
136
+ const newItem = {
137
+ id: `video_${result.timestamp}.${result.format}`,
138
+ type: result.format === 'gif' ? 'gif' : 'video',
139
+ format: result.format,
140
+ uri: result.uri,
141
+ filename: result.uri.split('/').pop() ||
142
+ `video_${result.timestamp}.${result.format}`,
143
+ sizeBytes: result.sizeBytes,
144
+ timestamp: result.timestamp,
145
+ durationMs: result.durationMs,
146
+ width: result.width,
147
+ height: result.height,
148
+ hasAudio: result.hasAudio,
149
+ };
150
+ setPreviewMediaItem(newItem);
151
+ refreshMediaCount?.().catch(() => { });
152
+ showToast(t('header.recordingSaved', {
153
+ duration: (result.durationMs / 1000).toFixed(1),
154
+ defaultValue: `Recording saved (${(result.durationMs / 1000).toFixed(1)}s)`,
155
+ }));
156
+ }
157
+ }
158
+ else {
159
+ triggerNativeHaptic('medium');
160
+ const started = await ScreenCapture.startRecording({
161
+ fps: 24,
162
+ audioSource: 'none',
163
+ });
164
+ if (started) {
165
+ setIsRecording(true);
166
+ showToast(t('header.recordingStarted', 'Recording started'));
167
+ }
168
+ else {
169
+ showToast(t('header.recordingStartFailed', 'Failed to start recording'));
170
+ }
171
+ }
172
+ }
173
+ catch {
174
+ showToast(t('header.recordingError', 'Error during screen recording'));
175
+ setIsRecording(false);
176
+ }
177
+ }, [isRecording, refreshMediaCount, t]);
178
+ const handleExpandInspector = useCallback(() => {
179
+ if (playerDraggedRef.current)
180
+ return;
181
+ triggerNativeHaptic('light');
182
+ setIsMinimized?.(false);
183
+ setVisible(true);
184
+ }, [setIsMinimized, setVisible]);
185
+ const formatTime = (secs) => {
186
+ const mins = Math.floor(secs / 60);
187
+ const remainingSecs = secs % 60;
188
+ return `${mins}:${remainingSecs < 10 ? '0' : ''}${remainingSecs}`;
189
+ };
190
+ return (<>
191
+
192
+ {isMinimized ? (<Animated.View style={[
193
+ fabStyles.playerContainer,
194
+ { transform: playerPan.getTranslateTransform() },
195
+ ]} {...playerPanResponder.panHandlers}>
196
+
197
+ <LinearGradient colors={[AppColors.indigo600, AppColors.violet600]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 1 }} style={fabStyles.gradientBg} pointerEvents="none"/>
198
+
199
+
200
+ <View pointerEvents="none" style={fabStyles.playerShineClip}>
201
+ <Animated.View style={[
202
+ fabStyles.playerShineStreak,
203
+ {
204
+ transform: [
205
+ {
206
+ translateX: fabShineAnim.interpolate({
207
+ inputRange: [0, 1],
208
+ outputRange: [-80, screenWidth + 80],
209
+ }),
210
+ },
211
+ { rotate: '28deg' },
212
+ ],
213
+ },
214
+ ]}>
215
+ <LinearGradient colors={[
216
+ 'rgba(255, 255, 255, 0)',
217
+ 'rgba(255, 255, 255, 0.18)',
218
+ 'rgba(255, 255, 255, 0.70)',
219
+ 'rgba(255, 255, 255, 1)',
220
+ 'rgba(255, 255, 255, 0.70)',
221
+ 'rgba(255, 255, 255, 0.18)',
222
+ 'rgba(255, 255, 255, 0)',
223
+ ]} locations={[0, 0.22, 0.44, 0.5, 0.56, 0.78, 1]} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} style={{ flex: 1 }}/>
224
+ </Animated.View>
225
+ </View>
226
+
227
+
228
+ <TouchableOpacity activeOpacity={0.75} onPress={handleExpandInspector} style={fabStyles.brandSection} accessibilityRole="button" accessibilityLabel={t('mediaGallery.expand', 'Expand Inspector')}>
229
+ <AppHeaderLogo size={36} customIcon={appIcon}/>
230
+ <View style={fabStyles.titleWrapper}>
231
+ <Text style={fabStyles.appTitle} numberOfLines={1}>
232
+ {getAppName()}
233
+ </Text>
234
+ <View style={fabStyles.versionRow}>
235
+
236
+ <View style={fabStyles.versionChip}>
237
+ {Platform.OS === 'ios' ? (<AppleIcon color={`${AppColors.white}E6`} size={9}/>) : (<AndroidIcon color={`${AppColors.white}E6`} size={9}/>)}
238
+ <Text style={fabStyles.versionChipText} numberOfLines={1}>
239
+ {appVersionString}
240
+ </Text>
241
+ </View>
242
+
243
+
244
+ <View style={fabStyles.versionChip}>
245
+ <NpmIcon size={9} color={AppColors.npmRedLight}/>
246
+ <Text style={fabStyles.versionChipText} numberOfLines={1}>
247
+ v{LIB_VERSION}
248
+ </Text>
249
+ </View>
250
+ </View>
251
+ </View>
252
+ </TouchableOpacity>
253
+
254
+
255
+ <View style={fabStyles.actionsRow}>
256
+
257
+ <TouchableScale onPress={handleTakeScreenshot} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={fabStyles.actionBtn}>
258
+ <CameraIcon size={13} color={AppColors.white}/>
259
+ <Text style={fabStyles.actionText}>
260
+ {t('header.photo', 'Photo')}
261
+ </Text>
262
+ </TouchableScale>
263
+
264
+
265
+ <TouchableScale onPress={handleToggleVideoRecording} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={[
266
+ fabStyles.actionBtn,
267
+ isRecording && fabStyles.recordingBtnActive,
268
+ ]}>
269
+ {isRecording ? (<View style={fabStyles.recordingDot}/>) : (<VideoCameraIcon size={13} color={AppColors.white}/>)}
270
+ <Text style={[
271
+ fabStyles.actionText,
272
+ isRecording && { color: AppColors.red300 },
273
+ ]}>
274
+ {isRecording
275
+ ? `${t('header.rec', 'REC')} ${formatTime(recordingSeconds)}`
276
+ : t('header.record', 'Record')}
277
+ </Text>
278
+ </TouchableScale>
279
+ </View>
280
+ </Animated.View>) : (<Animated.View style={[styles.fabWrapper, { transform: fabPan.getTranslateTransform() }]} {...fabPanResponder.panHandlers}>
281
+ <TouchableScale style={{ alignItems: 'center', justifyContent: 'center' }} onPress={() => {
282
+ if (fabDraggedRef.current)
283
+ return;
284
+ triggerNativeHaptic('light');
285
+ setVisible(true);
286
+ }} hitSlop={10}>
287
+ <Animated.View style={[styles.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
288
+ <BrandCircleIcon size={62}/>
289
+
290
+ <View pointerEvents="none" style={styles.fabShineClip}>
291
+ <Animated.View style={[
292
+ styles.fabShineStreak,
293
+ {
294
+ transform: [
295
+ {
296
+ translateX: fabShineAnim.interpolate({
297
+ inputRange: [0, 1],
298
+ outputRange: [-48, 96],
299
+ }),
300
+ },
301
+ { rotate: '25deg' },
302
+ ],
303
+ },
304
+ ]}>
305
+ <LinearGradient colors={[
306
+ 'rgba(255, 255, 255, 0)',
307
+ 'rgba(255, 255, 255, 0.25)',
308
+ 'rgba(255, 255, 255, 0.95)',
309
+ 'rgba(255, 255, 255, 0.25)',
310
+ 'rgba(255, 255, 255, 0)',
311
+ ]} locations={[0, 0.25, 0.5, 0.75, 1]} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} style={{ flex: 1 }}/>
312
+ </Animated.View>
313
+ </View>
314
+ {(logs.length > 0 || analyticsEvents.length > 0) && (<Animated.View style={[
315
+ styles.fabGreenDot,
316
+ { transform: [{ scale: unreadPulseAnim }] },
317
+ ]}/>)}
318
+ </TouchableScale>
319
+ </Animated.View>)}
320
+
321
+
322
+ <MediaPreviewModal item={previewMediaItem} visible={!!previewMediaItem} onClose={() => setPreviewMediaItem(null)} onDelete={async (item) => {
323
+ await ScreenCapture.deleteMedia(item.uri);
324
+ refreshMediaCount?.().catch(() => { });
325
+ setPreviewMediaItem(null);
326
+ }} onConvertToGif={async (item) => {
327
+ const gif = await ScreenCapture.convertToGif(item.uri, {
328
+ fps: 12,
329
+ width: 480,
330
+ });
331
+ if (gif) {
332
+ refreshMediaCount?.().catch(() => { });
333
+ setPreviewMediaItem({
334
+ id: `anim_${gif.timestamp}.gif`,
335
+ type: 'gif',
336
+ format: 'gif',
337
+ uri: gif.uri,
338
+ filename: gif.uri.split('/').pop() || `anim_${gif.timestamp}.gif`,
339
+ sizeBytes: gif.sizeBytes,
340
+ timestamp: gif.timestamp,
341
+ durationMs: gif.durationMs,
342
+ });
343
+ }
344
+ }}/>
345
+ </>);
48
346
  };
347
+ const fabStyles = StyleSheet.create({
348
+ playerContainer: {
349
+ position: 'absolute',
350
+ bottom: Platform.OS === 'ios' ? 34 : 18,
351
+ left: 12,
352
+ right: 12,
353
+ minHeight: 58,
354
+ flexDirection: 'row',
355
+ alignItems: 'center',
356
+ justifyContent: 'space-between',
357
+ borderRadius: 16,
358
+ borderWidth: 1,
359
+ borderColor: 'rgba(255, 255, 255, 0.28)',
360
+ paddingHorizontal: 12,
361
+ paddingVertical: 7,
362
+ gap: 8,
363
+ zIndex: 99999,
364
+ elevation: 25,
365
+ shadowColor: AppColors.indigo600,
366
+ shadowOffset: { width: 0, height: 8 },
367
+ shadowOpacity: 0.45,
368
+ shadowRadius: 14,
369
+ overflow: 'hidden',
370
+ },
371
+ gradientBg: {
372
+ position: 'absolute',
373
+ top: 0,
374
+ left: 0,
375
+ right: 0,
376
+ bottom: 0,
377
+ borderRadius: 16,
378
+ },
379
+ playerShineClip: {
380
+ position: 'absolute',
381
+ top: 0,
382
+ left: 0,
383
+ right: 0,
384
+ bottom: 0,
385
+ borderRadius: 16,
386
+ overflow: 'hidden',
387
+ },
388
+ playerShineStreak: {
389
+ position: 'absolute',
390
+ top: -60,
391
+ width: 75,
392
+ height: 200,
393
+ },
394
+ brandSection: {
395
+ flexDirection: 'row',
396
+ alignItems: 'center',
397
+ gap: 9,
398
+ flex: 1,
399
+ minWidth: 0,
400
+ paddingVertical: 2,
401
+ },
402
+ titleWrapper: {
403
+ flex: 1,
404
+ minWidth: 0,
405
+ justifyContent: 'center',
406
+ gap: 2,
407
+ },
408
+ appTitle: {
409
+ fontFamily: AppFonts.interBold,
410
+ fontSize: 13.5,
411
+ color: AppColors.white,
412
+ letterSpacing: -0.2,
413
+ },
414
+ versionRow: {
415
+ flexDirection: 'row',
416
+ alignItems: 'center',
417
+ gap: 4,
418
+ minWidth: 0,
419
+ },
420
+ versionChip: {
421
+ flexDirection: 'row',
422
+ alignItems: 'center',
423
+ backgroundColor: `${AppColors.white}1F`,
424
+ borderRadius: 5,
425
+ paddingHorizontal: 5,
426
+ paddingVertical: 1.5,
427
+ gap: 3,
428
+ borderWidth: 1,
429
+ borderColor: `${AppColors.white}2E`,
430
+ flexShrink: 1,
431
+ minWidth: 0,
432
+ },
433
+ versionChipText: {
434
+ fontFamily: AppFonts.interMedium,
435
+ fontSize: 8.5,
436
+ color: `${AppColors.white}EB`,
437
+ letterSpacing: 0.1,
438
+ },
439
+ actionsRow: {
440
+ flexDirection: 'row',
441
+ alignItems: 'center',
442
+ gap: 6,
443
+ },
444
+ actionBtn: {
445
+ flexDirection: 'row',
446
+ alignItems: 'center',
447
+ gap: 4.5,
448
+ backgroundColor: 'rgba(255, 255, 255, 0.22)',
449
+ paddingHorizontal: 10,
450
+ paddingVertical: 6,
451
+ borderRadius: 7,
452
+ borderWidth: 1,
453
+ borderColor: 'rgba(255, 255, 255, 0.32)',
454
+ },
455
+ recordingBtnActive: {
456
+ backgroundColor: 'rgba(239, 68, 68, 0.32)',
457
+ borderColor: AppColors.red500,
458
+ },
459
+ recordingDot: {
460
+ width: 6,
461
+ height: 6,
462
+ borderRadius: 3,
463
+ backgroundColor: AppColors.red500,
464
+ },
465
+ actionText: {
466
+ fontFamily: AppFonts.interSemiBold,
467
+ fontSize: 11,
468
+ color: AppColors.white,
469
+ },
470
+ });
49
471
  export default FabLauncher;