react-native-inapp-inspector 2.3.17 → 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
@@ -1021,6 +1021,30 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
1021
1021
  }
1022
1022
  }
1023
1023
 
1024
+ @ReactMethod
1025
+ fun playVideo(videoUri: String, promise: Promise) {
1026
+ val currentActivity = currentActivity
1027
+ if (currentActivity == null) {
1028
+ promise.reject("PLAY_ERROR", "No active activity found to play video")
1029
+ return
1030
+ }
1031
+ try {
1032
+ val uri = if (videoUri.startsWith("file://") || videoUri.startsWith("content://")) {
1033
+ Uri.parse(videoUri)
1034
+ } else {
1035
+ Uri.fromFile(File(videoUri))
1036
+ }
1037
+ val intent = Intent(Intent.ACTION_VIEW).apply {
1038
+ setDataAndType(uri, "video/*")
1039
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
1040
+ }
1041
+ currentActivity.startActivity(intent)
1042
+ promise.resolve(true)
1043
+ } catch (e: Exception) {
1044
+ promise.reject("PLAY_ERROR", e.message ?: "Failed to launch video player", e)
1045
+ }
1046
+ }
1047
+
1024
1048
  @ReactMethod
1025
1049
  fun getCapturedMedia(promise: Promise) {
1026
1050
  captureExecutor.execute {
@@ -1046,6 +1070,29 @@ class NetworkInspectorModule(private val reactContext: ReactApplicationContext)
1046
1070
  put("sizeBytes", file.length())
1047
1071
  put("timestamp", file.lastModified())
1048
1072
  }
1073
+
1074
+ if (type == "video") {
1075
+ val suffix = file.nameWithoutExtension.removePrefix("video_")
1076
+ val thumbFile = File(dir, "thumb_${suffix}.jpg")
1077
+ if (thumbFile.exists()) {
1078
+ obj.put("thumbnailUri", Uri.fromFile(thumbFile).toString())
1079
+ } else {
1080
+ try {
1081
+ val retriever = MediaMetadataRetriever()
1082
+ retriever.setDataSource(file.absolutePath)
1083
+ val bmp = retriever.getFrameAtTime(0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC)
1084
+ if (bmp != null) {
1085
+ FileOutputStream(thumbFile).use { fos ->
1086
+ bmp.compress(Bitmap.CompressFormat.JPEG, 80, fos)
1087
+ }
1088
+ bmp.recycle()
1089
+ obj.put("thumbnailUri", Uri.fromFile(thumbFile).toString())
1090
+ }
1091
+ retriever.release()
1092
+ } catch (e: Exception) {}
1093
+ }
1094
+ }
1095
+
1049
1096
  jsonArr.put(obj)
1050
1097
  }
1051
1098
 
@@ -8,6 +8,7 @@ export declare const ScreenCapture: {
8
8
  startRecording: (options?: RecordingOptions) => Promise<boolean>;
9
9
  stopRecording: () => Promise<RecordingResult | null>;
10
10
  isRecording: () => Promise<boolean>;
11
+ playVideo: (videoUri: string) => Promise<boolean>;
11
12
  convertToGif: (videoUri: string, options?: GifConversionOptions) => Promise<RecordingResult | null>;
12
13
  getMediaList: () => Promise<CapturedMediaItem[]>;
13
14
  deleteMedia: (uri: string) => Promise<boolean>;
@@ -18,6 +19,7 @@ export declare const ScreenRecorder: {
18
19
  startRecording: (options?: RecordingOptions) => Promise<boolean>;
19
20
  stopRecording: () => Promise<RecordingResult | null>;
20
21
  isRecording: () => Promise<boolean>;
22
+ playVideo: (videoUri: string) => Promise<boolean>;
21
23
  convertToGif: (videoUri: string, options?: GifConversionOptions) => Promise<RecordingResult | null>;
22
24
  getMediaList: () => Promise<CapturedMediaItem[]>;
23
25
  deleteMedia: (uri: string) => Promise<boolean>;
@@ -15,6 +15,9 @@ exports.ScreenCapture = {
15
15
  isRecording: async () => {
16
16
  return (0, NativeInspector_1.isNativeRecordingActive)();
17
17
  },
18
+ playVideo: async (videoUri) => {
19
+ return (0, NativeInspector_1.playNativeVideo)(videoUri);
20
+ },
18
21
  convertToGif: async (videoUri, options) => {
19
22
  return (0, NativeInspector_1.convertNativeVideoToGif)(videoUri, options);
20
23
  },
@@ -5,6 +5,7 @@ interface EmptyStateProps {
5
5
  customTitle?: string;
6
6
  customSub?: string;
7
7
  onClearSearch?: () => void;
8
+ showReload?: boolean;
8
9
  }
9
10
  declare const EmptyState: React.NamedExoticComponent<EmptyStateProps>;
10
11
  export default EmptyState;
@@ -43,7 +43,7 @@ const AnimatedEntrance_1 = __importDefault(require("./AnimatedEntrance"));
43
43
  const NetworkIcons_1 = require("./NetworkIcons");
44
44
  const AppColors_1 = require("../styles/AppColors");
45
45
  const styles_1 = __importDefault(require("../styles"));
46
- const EmptyState = react_1.default.memo(function EmptyState({ isSearch, searchQuery, customTitle, customSub, onClearSearch, }) {
46
+ const EmptyState = react_1.default.memo(function EmptyState({ isSearch, searchQuery, customTitle, customSub, onClearSearch, showReload = true, }) {
47
47
  const iconPulse = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current;
48
48
  (0, react_1.useEffect)(() => {
49
49
  const loop = react_native_1.Animated.loop(react_native_1.Animated.sequence([
@@ -86,7 +86,7 @@ const EmptyState = react_1.default.memo(function EmptyState({ isSearch, searchQu
86
86
  {isSearch && onClearSearch && (<TouchableScale_1.default style={styles_1.default.reloadBtn} onPress={onClearSearch}>
87
87
  <react_native_1.Text style={styles_1.default.reloadBtnText}>Clear Search & Filters</react_native_1.Text>
88
88
  </TouchableScale_1.default>)}
89
- {!isSearch && (<TouchableScale_1.default style={styles_1.default.reloadBtn} onPress={handleReload}>
89
+ {!isSearch && showReload && (<TouchableScale_1.default style={styles_1.default.reloadBtn} onPress={handleReload}>
90
90
  <react_native_1.Text style={styles_1.default.reloadBtnText}>Reload App</react_native_1.Text>
91
91
  </TouchableScale_1.default>)}
92
92
  </AnimatedEntrance_1.default>);
@@ -1,54 +1,509 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const react_1 = __importDefault(require("react"));
39
+ const react_1 = __importStar(require("react"));
7
40
  const react_native_1 = require("react-native");
8
41
  const react_native_linear_gradient_1 = __importDefault(require("react-native-linear-gradient"));
9
42
  const InspectorContext_1 = require("./InspectorContext");
10
43
  const TouchableScale_1 = __importDefault(require("../TouchableScale"));
44
+ const AppHeaderLogo_1 = __importDefault(require("../AppHeaderLogo"));
11
45
  const BrandCircleIcon_1 = __importDefault(require("../BrandCircleIcon"));
46
+ const NetworkIcons_1 = require("../NetworkIcons");
47
+ const capture_1 = require("../../capture");
48
+ const NativeInspector_1 = require("../../native/NativeInspector");
49
+ const toast_1 = require("../../helpers/toast");
50
+ const helpers_1 = require("../../helpers");
51
+ const constants_1 = require("../../constants");
12
52
  const AppColors_1 = require("../../styles/AppColors");
53
+ const AppFonts_1 = require("../../styles/AppFonts");
13
54
  const styles_1 = __importDefault(require("../../styles"));
55
+ const i18n_1 = require("../../i18n");
56
+ const MediaPreviewModal_1 = require("./MediaPreviewModal");
14
57
  const FabLauncher = () => {
15
- const { setVisible, fabPan, fabPanResponder, fabDraggedRef, pulseAnim, fabShineAnim, unreadPulseAnim, logs, analyticsEvents, } = (0, InspectorContext_1.useInspector)();
16
- return (<react_native_1.Animated.View style={[styles_1.default.fabWrapper, { transform: fabPan.getTranslateTransform() }]} {...fabPanResponder.panHandlers}>
17
- <TouchableScale_1.default style={{ alignItems: 'center', justifyContent: 'center' }} onPress={() => {
18
- if (fabDraggedRef.current)
19
- return;
20
- setVisible(true);
21
- }} hitSlop={10}>
22
- <react_native_1.Animated.View style={[styles_1.default.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
23
- <BrandCircleIcon_1.default size={62}/>
24
-
25
- <react_native_1.View pointerEvents="none" style={styles_1.default.fabShineClip}>
26
- <react_native_1.Animated.View style={[
27
- styles_1.default.fabShineStreak,
28
- {
29
- transform: [
30
- {
31
- translateX: fabShineAnim.interpolate({
32
- inputRange: [0, 1],
33
- outputRange: [-48, 96],
34
- }),
35
- },
36
- { rotate: '25deg' },
37
- ],
38
- },
39
- ]}>
40
- <react_native_linear_gradient_1.default colors={[
41
- `${AppColors_1.AppColors.white}00`,
42
- `${AppColors_1.AppColors.white}8C`,
43
- `${AppColors_1.AppColors.white}00`,
44
- ]} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} style={{ flex: 1 }}/>
45
- </react_native_1.Animated.View>
46
- </react_native_1.View>
47
- {(logs.length > 0 || analyticsEvents.length > 0) && (<react_native_1.Animated.View style={[
48
- styles_1.default.fabGreenDot,
49
- { transform: [{ scale: unreadPulseAnim }] },
50
- ]}/>)}
51
- </TouchableScale_1.default>
52
- </react_native_1.Animated.View>);
58
+ const { setVisible, isMinimized, setIsMinimized, appIcon, fabPan, fabPanResponder, fabDraggedRef, pulseAnim, fabShineAnim, unreadPulseAnim, logs, analyticsEvents, refreshMediaCount, } = (0, InspectorContext_1.useInspector)();
59
+ const { width: screenWidth } = (0, react_native_1.useWindowDimensions)();
60
+ const { t } = (0, i18n_1.useTranslation)();
61
+ const [appVersionString] = (0, react_1.useState)(() => {
62
+ return (0, helpers_1.getAppVersionAndBuild)().formatted;
63
+ });
64
+ const [isRecording, setIsRecording] = (0, react_1.useState)(false);
65
+ const [recordingSeconds, setRecordingSeconds] = (0, react_1.useState)(0);
66
+ const recordingTimerRef = (0, react_1.useRef)(null);
67
+ const [previewMediaItem, setPreviewMediaItem] = (0, react_1.useState)(null);
68
+ const playerPan = (0, react_1.useRef)(new react_native_1.Animated.ValueXY({ x: 0, y: 0 })).current;
69
+ const playerPanRef = (0, react_1.useRef)({ x: 0, y: 0 });
70
+ (0, react_1.useEffect)(() => {
71
+ const idX = playerPan.x.addListener(v => (playerPanRef.current.x = v.value));
72
+ const idY = playerPan.y.addListener(v => (playerPanRef.current.y = v.value));
73
+ return () => {
74
+ playerPan.x.removeListener(idX);
75
+ playerPan.y.removeListener(idY);
76
+ };
77
+ }, [playerPan]);
78
+ const playerDraggedRef = (0, react_1.useRef)(false);
79
+ const playerPanResponder = (0, react_1.useRef)(react_native_1.PanResponder.create({
80
+ onStartShouldSetPanResponder: () => false,
81
+ onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 4 || Math.abs(g.dy) > 4,
82
+ onPanResponderGrant: () => {
83
+ playerDraggedRef.current = true;
84
+ playerPan.setOffset({
85
+ x: playerPanRef.current.x,
86
+ y: playerPanRef.current.y,
87
+ });
88
+ playerPan.setValue({ x: 0, y: 0 });
89
+ },
90
+ onPanResponderMove: react_native_1.Animated.event([null, { dx: playerPan.x, dy: playerPan.y }], { useNativeDriver: false }),
91
+ onPanResponderRelease: () => {
92
+ playerPan.flattenOffset();
93
+ setTimeout(() => {
94
+ playerDraggedRef.current = false;
95
+ }, 50);
96
+ },
97
+ onPanResponderTerminate: () => {
98
+ playerPan.flattenOffset();
99
+ playerDraggedRef.current = false;
100
+ },
101
+ })).current;
102
+ (0, react_1.useEffect)(() => {
103
+ capture_1.ScreenCapture.isRecording()
104
+ .then(active => {
105
+ setIsRecording(Boolean(active));
106
+ })
107
+ .catch(() => { });
108
+ }, []);
109
+ (0, react_1.useEffect)(() => {
110
+ if (isRecording) {
111
+ setRecordingSeconds(0);
112
+ recordingTimerRef.current = setInterval(() => {
113
+ setRecordingSeconds(prev => prev + 1);
114
+ }, 1000);
115
+ }
116
+ else {
117
+ if (recordingTimerRef.current) {
118
+ clearInterval(recordingTimerRef.current);
119
+ recordingTimerRef.current = null;
120
+ }
121
+ setRecordingSeconds(0);
122
+ }
123
+ return () => {
124
+ if (recordingTimerRef.current) {
125
+ clearInterval(recordingTimerRef.current);
126
+ }
127
+ };
128
+ }, [isRecording]);
129
+ const handleTakeScreenshot = (0, react_1.useCallback)(async () => {
130
+ if (playerDraggedRef.current)
131
+ return;
132
+ try {
133
+ (0, NativeInspector_1.triggerNativeHaptic)('light');
134
+ const result = await capture_1.ScreenCapture.takeScreenshot({
135
+ format: 'png',
136
+ quality: 0.9,
137
+ hideInspector: true,
138
+ });
139
+ if (result) {
140
+ (0, NativeInspector_1.triggerNativeHaptic)('success');
141
+ const newItem = {
142
+ id: `screenshot_${result.timestamp}.png`,
143
+ type: 'image',
144
+ format: result.format,
145
+ uri: result.uri,
146
+ filename: result.uri.split('/').pop() || `screenshot_${result.timestamp}.png`,
147
+ sizeBytes: result.sizeBytes,
148
+ timestamp: result.timestamp,
149
+ width: result.width,
150
+ height: result.height,
151
+ };
152
+ setPreviewMediaItem(newItem);
153
+ refreshMediaCount?.().catch(() => { });
154
+ (0, toast_1.showToast)(t('header.screenshotCaptured', 'Screenshot captured'));
155
+ }
156
+ else {
157
+ (0, toast_1.showToast)(t('header.screenshotFailed', 'Failed to capture screenshot'));
158
+ }
159
+ }
160
+ catch {
161
+ (0, toast_1.showToast)(t('header.screenshotError', 'Error capturing screenshot'));
162
+ }
163
+ }, [refreshMediaCount, t]);
164
+ const handleToggleVideoRecording = (0, react_1.useCallback)(async () => {
165
+ if (playerDraggedRef.current)
166
+ return;
167
+ try {
168
+ if (isRecording) {
169
+ (0, NativeInspector_1.triggerNativeHaptic)('medium');
170
+ const result = await capture_1.ScreenCapture.stopRecording();
171
+ setIsRecording(false);
172
+ if (result) {
173
+ (0, NativeInspector_1.triggerNativeHaptic)('success');
174
+ const newItem = {
175
+ id: `video_${result.timestamp}.${result.format}`,
176
+ type: result.format === 'gif' ? 'gif' : 'video',
177
+ format: result.format,
178
+ uri: result.uri,
179
+ filename: result.uri.split('/').pop() ||
180
+ `video_${result.timestamp}.${result.format}`,
181
+ sizeBytes: result.sizeBytes,
182
+ timestamp: result.timestamp,
183
+ durationMs: result.durationMs,
184
+ width: result.width,
185
+ height: result.height,
186
+ hasAudio: result.hasAudio,
187
+ };
188
+ setPreviewMediaItem(newItem);
189
+ refreshMediaCount?.().catch(() => { });
190
+ (0, toast_1.showToast)(t('header.recordingSaved', {
191
+ duration: (result.durationMs / 1000).toFixed(1),
192
+ defaultValue: `Recording saved (${(result.durationMs / 1000).toFixed(1)}s)`,
193
+ }));
194
+ }
195
+ }
196
+ else {
197
+ (0, NativeInspector_1.triggerNativeHaptic)('medium');
198
+ const started = await capture_1.ScreenCapture.startRecording({
199
+ fps: 24,
200
+ audioSource: 'none',
201
+ });
202
+ if (started) {
203
+ setIsRecording(true);
204
+ (0, toast_1.showToast)(t('header.recordingStarted', 'Recording started'));
205
+ }
206
+ else {
207
+ (0, toast_1.showToast)(t('header.recordingStartFailed', 'Failed to start recording'));
208
+ }
209
+ }
210
+ }
211
+ catch {
212
+ (0, toast_1.showToast)(t('header.recordingError', 'Error during screen recording'));
213
+ setIsRecording(false);
214
+ }
215
+ }, [isRecording, refreshMediaCount, t]);
216
+ const handleExpandInspector = (0, react_1.useCallback)(() => {
217
+ if (playerDraggedRef.current)
218
+ return;
219
+ (0, NativeInspector_1.triggerNativeHaptic)('light');
220
+ setIsMinimized?.(false);
221
+ setVisible(true);
222
+ }, [setIsMinimized, setVisible]);
223
+ const formatTime = (secs) => {
224
+ const mins = Math.floor(secs / 60);
225
+ const remainingSecs = secs % 60;
226
+ return `${mins}:${remainingSecs < 10 ? '0' : ''}${remainingSecs}`;
227
+ };
228
+ return (<>
229
+
230
+ {isMinimized ? (<react_native_1.Animated.View style={[
231
+ fabStyles.playerContainer,
232
+ { transform: playerPan.getTranslateTransform() },
233
+ ]} {...playerPanResponder.panHandlers}>
234
+
235
+ <react_native_linear_gradient_1.default colors={[AppColors_1.AppColors.indigo600, AppColors_1.AppColors.violet600]} start={{ x: 0, y: 0 }} end={{ x: 1, y: 1 }} style={fabStyles.gradientBg} pointerEvents="none"/>
236
+
237
+
238
+ <react_native_1.View pointerEvents="none" style={fabStyles.playerShineClip}>
239
+ <react_native_1.Animated.View style={[
240
+ fabStyles.playerShineStreak,
241
+ {
242
+ transform: [
243
+ {
244
+ translateX: fabShineAnim.interpolate({
245
+ inputRange: [0, 1],
246
+ outputRange: [-80, screenWidth + 80],
247
+ }),
248
+ },
249
+ { rotate: '28deg' },
250
+ ],
251
+ },
252
+ ]}>
253
+ <react_native_linear_gradient_1.default colors={[
254
+ 'rgba(255, 255, 255, 0)',
255
+ 'rgba(255, 255, 255, 0.18)',
256
+ 'rgba(255, 255, 255, 0.70)',
257
+ 'rgba(255, 255, 255, 1)',
258
+ 'rgba(255, 255, 255, 0.70)',
259
+ 'rgba(255, 255, 255, 0.18)',
260
+ 'rgba(255, 255, 255, 0)',
261
+ ]} 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 }}/>
262
+ </react_native_1.Animated.View>
263
+ </react_native_1.View>
264
+
265
+
266
+ <react_native_1.TouchableOpacity activeOpacity={0.75} onPress={handleExpandInspector} style={fabStyles.brandSection} accessibilityRole="button" accessibilityLabel={t('mediaGallery.expand', 'Expand Inspector')}>
267
+ <AppHeaderLogo_1.default size={36} customIcon={appIcon}/>
268
+ <react_native_1.View style={fabStyles.titleWrapper}>
269
+ <react_native_1.Text style={fabStyles.appTitle} numberOfLines={1}>
270
+ {(0, helpers_1.getAppName)()}
271
+ </react_native_1.Text>
272
+ <react_native_1.View style={fabStyles.versionRow}>
273
+
274
+ <react_native_1.View style={fabStyles.versionChip}>
275
+ {react_native_1.Platform.OS === 'ios' ? (<NetworkIcons_1.AppleIcon color={`${AppColors_1.AppColors.white}E6`} size={9}/>) : (<NetworkIcons_1.AndroidIcon color={`${AppColors_1.AppColors.white}E6`} size={9}/>)}
276
+ <react_native_1.Text style={fabStyles.versionChipText} numberOfLines={1}>
277
+ {appVersionString}
278
+ </react_native_1.Text>
279
+ </react_native_1.View>
280
+
281
+
282
+ <react_native_1.View style={fabStyles.versionChip}>
283
+ <NetworkIcons_1.NpmIcon size={9} color={AppColors_1.AppColors.npmRedLight}/>
284
+ <react_native_1.Text style={fabStyles.versionChipText} numberOfLines={1}>
285
+ v{constants_1.LIB_VERSION}
286
+ </react_native_1.Text>
287
+ </react_native_1.View>
288
+ </react_native_1.View>
289
+ </react_native_1.View>
290
+ </react_native_1.TouchableOpacity>
291
+
292
+
293
+ <react_native_1.View style={fabStyles.actionsRow}>
294
+
295
+ <TouchableScale_1.default onPress={handleTakeScreenshot} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={fabStyles.actionBtn}>
296
+ <NetworkIcons_1.CameraIcon size={13} color={AppColors_1.AppColors.white}/>
297
+ <react_native_1.Text style={fabStyles.actionText}>
298
+ {t('header.photo', 'Photo')}
299
+ </react_native_1.Text>
300
+ </TouchableScale_1.default>
301
+
302
+
303
+ <TouchableScale_1.default onPress={handleToggleVideoRecording} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} style={[
304
+ fabStyles.actionBtn,
305
+ isRecording && fabStyles.recordingBtnActive,
306
+ ]}>
307
+ {isRecording ? (<react_native_1.View style={fabStyles.recordingDot}/>) : (<NetworkIcons_1.VideoCameraIcon size={13} color={AppColors_1.AppColors.white}/>)}
308
+ <react_native_1.Text style={[
309
+ fabStyles.actionText,
310
+ isRecording && { color: AppColors_1.AppColors.red300 },
311
+ ]}>
312
+ {isRecording
313
+ ? `${t('header.rec', 'REC')} ${formatTime(recordingSeconds)}`
314
+ : t('header.record', 'Record')}
315
+ </react_native_1.Text>
316
+ </TouchableScale_1.default>
317
+ </react_native_1.View>
318
+ </react_native_1.Animated.View>) : (<react_native_1.Animated.View style={[styles_1.default.fabWrapper, { transform: fabPan.getTranslateTransform() }]} {...fabPanResponder.panHandlers}>
319
+ <TouchableScale_1.default style={{ alignItems: 'center', justifyContent: 'center' }} onPress={() => {
320
+ if (fabDraggedRef.current)
321
+ return;
322
+ (0, NativeInspector_1.triggerNativeHaptic)('light');
323
+ setVisible(true);
324
+ }} hitSlop={10}>
325
+ <react_native_1.Animated.View style={[styles_1.default.fabPulseRing, { transform: [{ scale: pulseAnim }] }]}/>
326
+ <BrandCircleIcon_1.default size={62}/>
327
+
328
+ <react_native_1.View pointerEvents="none" style={styles_1.default.fabShineClip}>
329
+ <react_native_1.Animated.View style={[
330
+ styles_1.default.fabShineStreak,
331
+ {
332
+ transform: [
333
+ {
334
+ translateX: fabShineAnim.interpolate({
335
+ inputRange: [0, 1],
336
+ outputRange: [-48, 96],
337
+ }),
338
+ },
339
+ { rotate: '25deg' },
340
+ ],
341
+ },
342
+ ]}>
343
+ <react_native_linear_gradient_1.default colors={[
344
+ 'rgba(255, 255, 255, 0)',
345
+ 'rgba(255, 255, 255, 0.25)',
346
+ 'rgba(255, 255, 255, 0.95)',
347
+ 'rgba(255, 255, 255, 0.25)',
348
+ 'rgba(255, 255, 255, 0)',
349
+ ]} locations={[0, 0.25, 0.5, 0.75, 1]} start={{ x: 0, y: 0.5 }} end={{ x: 1, y: 0.5 }} style={{ flex: 1 }}/>
350
+ </react_native_1.Animated.View>
351
+ </react_native_1.View>
352
+ {(logs.length > 0 || analyticsEvents.length > 0) && (<react_native_1.Animated.View style={[
353
+ styles_1.default.fabGreenDot,
354
+ { transform: [{ scale: unreadPulseAnim }] },
355
+ ]}/>)}
356
+ </TouchableScale_1.default>
357
+ </react_native_1.Animated.View>)}
358
+
359
+
360
+ <MediaPreviewModal_1.MediaPreviewModal item={previewMediaItem} visible={!!previewMediaItem} onClose={() => setPreviewMediaItem(null)} onDelete={async (item) => {
361
+ await capture_1.ScreenCapture.deleteMedia(item.uri);
362
+ refreshMediaCount?.().catch(() => { });
363
+ setPreviewMediaItem(null);
364
+ }} onConvertToGif={async (item) => {
365
+ const gif = await capture_1.ScreenCapture.convertToGif(item.uri, {
366
+ fps: 12,
367
+ width: 480,
368
+ });
369
+ if (gif) {
370
+ refreshMediaCount?.().catch(() => { });
371
+ setPreviewMediaItem({
372
+ id: `anim_${gif.timestamp}.gif`,
373
+ type: 'gif',
374
+ format: 'gif',
375
+ uri: gif.uri,
376
+ filename: gif.uri.split('/').pop() || `anim_${gif.timestamp}.gif`,
377
+ sizeBytes: gif.sizeBytes,
378
+ timestamp: gif.timestamp,
379
+ durationMs: gif.durationMs,
380
+ });
381
+ }
382
+ }}/>
383
+ </>);
53
384
  };
385
+ const fabStyles = react_native_1.StyleSheet.create({
386
+ playerContainer: {
387
+ position: 'absolute',
388
+ bottom: react_native_1.Platform.OS === 'ios' ? 34 : 18,
389
+ left: 12,
390
+ right: 12,
391
+ minHeight: 58,
392
+ flexDirection: 'row',
393
+ alignItems: 'center',
394
+ justifyContent: 'space-between',
395
+ borderRadius: 16,
396
+ borderWidth: 1,
397
+ borderColor: 'rgba(255, 255, 255, 0.28)',
398
+ paddingHorizontal: 12,
399
+ paddingVertical: 7,
400
+ gap: 8,
401
+ zIndex: 99999,
402
+ elevation: 25,
403
+ shadowColor: AppColors_1.AppColors.indigo600,
404
+ shadowOffset: { width: 0, height: 8 },
405
+ shadowOpacity: 0.45,
406
+ shadowRadius: 14,
407
+ overflow: 'hidden',
408
+ },
409
+ gradientBg: {
410
+ position: 'absolute',
411
+ top: 0,
412
+ left: 0,
413
+ right: 0,
414
+ bottom: 0,
415
+ borderRadius: 16,
416
+ },
417
+ playerShineClip: {
418
+ position: 'absolute',
419
+ top: 0,
420
+ left: 0,
421
+ right: 0,
422
+ bottom: 0,
423
+ borderRadius: 16,
424
+ overflow: 'hidden',
425
+ },
426
+ playerShineStreak: {
427
+ position: 'absolute',
428
+ top: -60,
429
+ width: 75,
430
+ height: 200,
431
+ },
432
+ brandSection: {
433
+ flexDirection: 'row',
434
+ alignItems: 'center',
435
+ gap: 9,
436
+ flex: 1,
437
+ minWidth: 0,
438
+ paddingVertical: 2,
439
+ },
440
+ titleWrapper: {
441
+ flex: 1,
442
+ minWidth: 0,
443
+ justifyContent: 'center',
444
+ gap: 2,
445
+ },
446
+ appTitle: {
447
+ fontFamily: AppFonts_1.AppFonts.interBold,
448
+ fontSize: 13.5,
449
+ color: AppColors_1.AppColors.white,
450
+ letterSpacing: -0.2,
451
+ },
452
+ versionRow: {
453
+ flexDirection: 'row',
454
+ alignItems: 'center',
455
+ gap: 4,
456
+ minWidth: 0,
457
+ },
458
+ versionChip: {
459
+ flexDirection: 'row',
460
+ alignItems: 'center',
461
+ backgroundColor: `${AppColors_1.AppColors.white}1F`,
462
+ borderRadius: 5,
463
+ paddingHorizontal: 5,
464
+ paddingVertical: 1.5,
465
+ gap: 3,
466
+ borderWidth: 1,
467
+ borderColor: `${AppColors_1.AppColors.white}2E`,
468
+ flexShrink: 1,
469
+ minWidth: 0,
470
+ },
471
+ versionChipText: {
472
+ fontFamily: AppFonts_1.AppFonts.interMedium,
473
+ fontSize: 8.5,
474
+ color: `${AppColors_1.AppColors.white}EB`,
475
+ letterSpacing: 0.1,
476
+ },
477
+ actionsRow: {
478
+ flexDirection: 'row',
479
+ alignItems: 'center',
480
+ gap: 6,
481
+ },
482
+ actionBtn: {
483
+ flexDirection: 'row',
484
+ alignItems: 'center',
485
+ gap: 4.5,
486
+ backgroundColor: 'rgba(255, 255, 255, 0.22)',
487
+ paddingHorizontal: 10,
488
+ paddingVertical: 6,
489
+ borderRadius: 7,
490
+ borderWidth: 1,
491
+ borderColor: 'rgba(255, 255, 255, 0.32)',
492
+ },
493
+ recordingBtnActive: {
494
+ backgroundColor: 'rgba(239, 68, 68, 0.32)',
495
+ borderColor: AppColors_1.AppColors.red500,
496
+ },
497
+ recordingDot: {
498
+ width: 6,
499
+ height: 6,
500
+ borderRadius: 3,
501
+ backgroundColor: AppColors_1.AppColors.red500,
502
+ },
503
+ actionText: {
504
+ fontFamily: AppFonts_1.AppFonts.interSemiBold,
505
+ fontSize: 11,
506
+ color: AppColors_1.AppColors.white,
507
+ },
508
+ });
54
509
  exports.default = FabLauncher;