react-native-iinstall 0.2.11 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/IInstallWrapper.d.ts +3 -1
- package/lib/IInstallWrapper.js +10 -159
- package/lib/index.d.ts +2 -0
- package/lib/index.js +60 -3
- package/package.json +1 -1
- package/src/IInstallWrapper.tsx +19 -157
- package/src/index.tsx +77 -3
package/lib/IInstallWrapper.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ interface IInstallWrapperProps {
|
|
|
5
5
|
children?: React.ReactNode;
|
|
6
6
|
enabled?: boolean;
|
|
7
7
|
showDebugButton?: boolean;
|
|
8
|
+
showFloatingButtonOnEmulator?: boolean;
|
|
9
|
+
floatingButtonLabel?: string;
|
|
8
10
|
}
|
|
9
11
|
export declare const IInstallWrapper: React.FC<IInstallWrapperProps>;
|
|
10
|
-
export
|
|
12
|
+
export default IInstallWrapper;
|
package/lib/IInstallWrapper.js
CHANGED
|
@@ -1,167 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
35
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
6
|
exports.IInstallWrapper = void 0;
|
|
37
|
-
const react_1 =
|
|
38
|
-
const react_native_1 = require("react-native");
|
|
39
|
-
// Import from the local SDK files instead of the package
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
40
8
|
const index_1 = require("./index");
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return (react_native_1.Platform.OS === 'ios' &&
|
|
47
|
-
(react_native_1.Platform.isPad || react_native_1.Platform.isTV || isDev)) || (react_native_1.Platform.OS === 'android' && isDev);
|
|
48
|
-
};
|
|
49
|
-
// Helper to detect if native sensors are available
|
|
50
|
-
const hasNativeSensors = () => {
|
|
51
|
-
try {
|
|
52
|
-
// Check if we're in a simulator/emulator environment
|
|
53
|
-
if (isSimulator()) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
// Additional check for Android emulators
|
|
57
|
-
if (react_native_1.Platform.OS === 'android') {
|
|
58
|
-
// Android emulators typically don't have motion sensors
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
// iOS simulators don't have motion sensors
|
|
62
|
-
if (react_native_1.Platform.OS === 'ios' && react_native_1.Platform.isPad) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
catch (e) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
const IInstallWrapper = ({ apiKey, apiEndpoint = 'https://iinstall.app', children, enabled = true, showDebugButton = true }) => {
|
|
72
|
-
const shakeDetectorRef = (0, react_1.useRef)(null);
|
|
73
|
-
// Calculate sensor availability once to avoid state updates
|
|
74
|
-
const sensorsAvailable = enabled && hasNativeSensors();
|
|
75
|
-
const showManualButton = !sensorsAvailable && enabled;
|
|
76
|
-
(0, react_1.useEffect)(() => {
|
|
77
|
-
if (!enabled)
|
|
78
|
-
return;
|
|
79
|
-
// Only use shake detector if sensors are available
|
|
80
|
-
if (sensorsAvailable) {
|
|
81
|
-
shakeDetectorRef.current = new ShakeDetector_1.ShakeDetector(() => {
|
|
82
|
-
// This will trigger the IInstall modal
|
|
83
|
-
// The shake detector will handle the screenshot capture
|
|
84
|
-
});
|
|
85
|
-
shakeDetectorRef.current.start();
|
|
86
|
-
}
|
|
87
|
-
return () => {
|
|
88
|
-
shakeDetectorRef.current?.stop();
|
|
89
|
-
};
|
|
90
|
-
}, [enabled, sensorsAvailable]);
|
|
91
|
-
const handleManualReport = () => {
|
|
92
|
-
// Manually trigger the IInstall feedback modal
|
|
93
|
-
// This simulates a shake gesture
|
|
94
|
-
if (enabled) {
|
|
95
|
-
// The IInstall component will handle the screenshot and modal
|
|
96
|
-
// We can trigger it by simulating the shake event
|
|
97
|
-
console.log('[IInstall] Manual report triggered');
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
const handleDebugInfo = () => {
|
|
101
|
-
const debugMessage = [
|
|
102
|
-
`Platform: ${react_native_1.Platform.OS}`,
|
|
103
|
-
`Is Simulator: ${isSimulator() ? 'Yes' : 'No'}`,
|
|
104
|
-
`Native Sensors: ${sensorsAvailable ? 'Available' : 'Not Available'}`,
|
|
105
|
-
`Manual Button: ${showManualButton ? 'Visible' : 'Hidden'}`,
|
|
106
|
-
`Shake Detection: ${sensorsAvailable ? 'Enabled' : 'Disabled'}`,
|
|
107
|
-
].join('\n');
|
|
108
|
-
react_native_1.Alert.alert('IInstall Debug Info', debugMessage, [{ text: 'OK' }]);
|
|
109
|
-
};
|
|
110
|
-
return (<index_1.IInstall apiKey={apiKey} apiEndpoint={apiEndpoint} enabled={enabled}>
|
|
9
|
+
// Backward-compatible wrapper. The core IInstall component now handles:
|
|
10
|
+
// - shake gesture on real devices
|
|
11
|
+
// - floating manual trigger button on simulator/emulator
|
|
12
|
+
const IInstallWrapper = ({ apiKey, apiEndpoint = 'https://iinstall.app', children, enabled = true, showDebugButton: _showDebugButton = false, showFloatingButtonOnEmulator = true, floatingButtonLabel = 'Report Issue', }) => {
|
|
13
|
+
return (<index_1.IInstall apiKey={apiKey} apiEndpoint={apiEndpoint} enabled={enabled} showFloatingButtonOnEmulator={showFloatingButtonOnEmulator} floatingButtonLabel={floatingButtonLabel}>
|
|
111
14
|
{children}
|
|
112
|
-
|
|
113
|
-
{showManualButton && (<react_native_1.View style={styles.manualButtonContainer}>
|
|
114
|
-
<react_native_1.TouchableOpacity style={styles.manualButton} onPress={handleManualReport}>
|
|
115
|
-
<react_native_1.Text style={styles.manualButtonText}>🐛 Report Issue</react_native_1.Text>
|
|
116
|
-
</react_native_1.TouchableOpacity>
|
|
117
|
-
|
|
118
|
-
{showDebugButton && (<react_native_1.TouchableOpacity style={styles.debugButton} onPress={handleDebugInfo}>
|
|
119
|
-
<react_native_1.Text style={styles.debugButtonText}>ℹ️</react_native_1.Text>
|
|
120
|
-
</react_native_1.TouchableOpacity>)}
|
|
121
|
-
</react_native_1.View>)}
|
|
122
15
|
</index_1.IInstall>);
|
|
123
16
|
};
|
|
124
17
|
exports.IInstallWrapper = IInstallWrapper;
|
|
125
|
-
|
|
126
|
-
manualButtonContainer: {
|
|
127
|
-
position: 'absolute',
|
|
128
|
-
bottom: 40,
|
|
129
|
-
right: 20,
|
|
130
|
-
flexDirection: 'row',
|
|
131
|
-
alignItems: 'center',
|
|
132
|
-
},
|
|
133
|
-
manualButton: {
|
|
134
|
-
backgroundColor: '#FF6B6B',
|
|
135
|
-
paddingHorizontal: 16,
|
|
136
|
-
paddingVertical: 12,
|
|
137
|
-
borderRadius: 25,
|
|
138
|
-
elevation: 5,
|
|
139
|
-
shadowColor: '#000',
|
|
140
|
-
shadowOffset: { width: 0, height: 2 },
|
|
141
|
-
shadowOpacity: 0.2,
|
|
142
|
-
shadowRadius: 4,
|
|
143
|
-
marginRight: 10,
|
|
144
|
-
},
|
|
145
|
-
manualButtonText: {
|
|
146
|
-
color: 'white',
|
|
147
|
-
fontSize: 14,
|
|
148
|
-
fontWeight: '600',
|
|
149
|
-
},
|
|
150
|
-
debugButton: {
|
|
151
|
-
backgroundColor: '#4ECDC4',
|
|
152
|
-
width: 40,
|
|
153
|
-
height: 40,
|
|
154
|
-
borderRadius: 20,
|
|
155
|
-
justifyContent: 'center',
|
|
156
|
-
alignItems: 'center',
|
|
157
|
-
elevation: 5,
|
|
158
|
-
shadowColor: '#000',
|
|
159
|
-
shadowOffset: { width: 0, height: 2 },
|
|
160
|
-
shadowOpacity: 0.2,
|
|
161
|
-
shadowRadius: 4,
|
|
162
|
-
},
|
|
163
|
-
debugButtonText: {
|
|
164
|
-
color: 'white',
|
|
165
|
-
fontSize: 16,
|
|
166
|
-
},
|
|
167
|
-
});
|
|
18
|
+
exports.default = exports.IInstallWrapper;
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -43,11 +43,13 @@ const react_native_view_shot_1 = require("react-native-view-shot");
|
|
|
43
43
|
const ShakeDetector_1 = require("./ShakeDetector");
|
|
44
44
|
const FeedbackModal_1 = require("./FeedbackModal");
|
|
45
45
|
const react_native_record_screen_1 = __importDefault(require("react-native-record-screen"));
|
|
46
|
-
const
|
|
46
|
+
const react_native_device_info_1 = __importDefault(require("react-native-device-info"));
|
|
47
|
+
const IInstall = ({ apiKey, apiEndpoint = 'https://iinstall.app', children, enabled = true, showFloatingButtonOnEmulator = true, floatingButtonLabel = 'Report Issue', }) => {
|
|
47
48
|
const [modalVisible, setModalVisible] = (0, react_1.useState)(false);
|
|
48
49
|
const [screenshotUri, setScreenshotUri] = (0, react_1.useState)(null);
|
|
49
50
|
const [videoUri, setVideoUri] = (0, react_1.useState)(null);
|
|
50
51
|
const [isRecording, setIsRecording] = (0, react_1.useState)(false);
|
|
52
|
+
const [isEmulator, setIsEmulator] = (0, react_1.useState)(false);
|
|
51
53
|
const shakeDetectorRef = (0, react_1.useRef)(null);
|
|
52
54
|
// Refs for stable access in shake callback
|
|
53
55
|
const isRecordingRef = (0, react_1.useRef)(isRecording);
|
|
@@ -56,6 +58,23 @@ const IInstall = ({ apiKey, apiEndpoint = 'https://iinstall.app', children, enab
|
|
|
56
58
|
isRecordingRef.current = isRecording;
|
|
57
59
|
modalVisibleRef.current = modalVisible;
|
|
58
60
|
}, [isRecording, modalVisible]);
|
|
61
|
+
(0, react_1.useEffect)(() => {
|
|
62
|
+
let mounted = true;
|
|
63
|
+
react_native_device_info_1.default.isEmulator()
|
|
64
|
+
.then((value) => {
|
|
65
|
+
if (mounted) {
|
|
66
|
+
setIsEmulator(value);
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
.catch(() => {
|
|
70
|
+
if (mounted) {
|
|
71
|
+
setIsEmulator(false);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return () => {
|
|
75
|
+
mounted = false;
|
|
76
|
+
};
|
|
77
|
+
}, []);
|
|
59
78
|
const handleShake = async () => {
|
|
60
79
|
if (modalVisibleRef.current || isRecordingRef.current)
|
|
61
80
|
return;
|
|
@@ -80,7 +99,7 @@ const IInstall = ({ apiKey, apiEndpoint = 'https://iinstall.app', children, enab
|
|
|
80
99
|
handleShakeCallback.current = handleShake;
|
|
81
100
|
});
|
|
82
101
|
(0, react_1.useEffect)(() => {
|
|
83
|
-
if (!enabled)
|
|
102
|
+
if (!enabled || isEmulator)
|
|
84
103
|
return;
|
|
85
104
|
// Use a wrapper to call the current handleShake
|
|
86
105
|
shakeDetectorRef.current = new ShakeDetector_1.ShakeDetector(() => handleShakeCallback.current());
|
|
@@ -88,7 +107,15 @@ const IInstall = ({ apiKey, apiEndpoint = 'https://iinstall.app', children, enab
|
|
|
88
107
|
return () => {
|
|
89
108
|
shakeDetectorRef.current?.stop();
|
|
90
109
|
};
|
|
91
|
-
}, [enabled]);
|
|
110
|
+
}, [enabled, isEmulator]);
|
|
111
|
+
const handleFloatingButtonPress = async () => {
|
|
112
|
+
await handleShakeCallback.current();
|
|
113
|
+
};
|
|
114
|
+
const shouldShowFloatingButton = enabled &&
|
|
115
|
+
isEmulator &&
|
|
116
|
+
showFloatingButtonOnEmulator &&
|
|
117
|
+
!modalVisible &&
|
|
118
|
+
!isRecording;
|
|
92
119
|
const normalizeVideoUri = (value) => {
|
|
93
120
|
if (react_native_1.Platform.OS === 'ios' && value.startsWith('/')) {
|
|
94
121
|
return `file://${value}`;
|
|
@@ -173,6 +200,12 @@ const IInstall = ({ apiKey, apiEndpoint = 'https://iinstall.app', children, enab
|
|
|
173
200
|
<react_native_1.View style={react_native_1.StyleSheet.absoluteFill}>
|
|
174
201
|
{children}
|
|
175
202
|
</react_native_1.View>
|
|
203
|
+
|
|
204
|
+
{shouldShowFloatingButton && (<react_native_1.View style={styles.floatingButtonContainer} pointerEvents="box-none">
|
|
205
|
+
<react_native_1.TouchableOpacity accessibilityRole="button" accessibilityLabel="Open feedback menu" onPress={handleFloatingButtonPress} style={styles.floatingButton}>
|
|
206
|
+
<react_native_1.Text style={styles.floatingButtonText}>{floatingButtonLabel}</react_native_1.Text>
|
|
207
|
+
</react_native_1.TouchableOpacity>
|
|
208
|
+
</react_native_1.View>)}
|
|
176
209
|
|
|
177
210
|
{isRecording && (<react_native_1.SafeAreaView style={styles.recordingOverlay} pointerEvents="box-none">
|
|
178
211
|
<react_native_1.View style={styles.recordingContainer}>
|
|
@@ -193,6 +226,30 @@ const IInstall = ({ apiKey, apiEndpoint = 'https://iinstall.app', children, enab
|
|
|
193
226
|
};
|
|
194
227
|
exports.IInstall = IInstall;
|
|
195
228
|
const styles = react_native_1.StyleSheet.create({
|
|
229
|
+
floatingButtonContainer: {
|
|
230
|
+
position: 'absolute',
|
|
231
|
+
right: 16,
|
|
232
|
+
bottom: 28,
|
|
233
|
+
zIndex: 9998,
|
|
234
|
+
elevation: 20,
|
|
235
|
+
},
|
|
236
|
+
floatingButton: {
|
|
237
|
+
backgroundColor: '#0f172a',
|
|
238
|
+
borderColor: '#2563eb',
|
|
239
|
+
borderWidth: 1,
|
|
240
|
+
borderRadius: 24,
|
|
241
|
+
paddingHorizontal: 14,
|
|
242
|
+
paddingVertical: 10,
|
|
243
|
+
shadowColor: '#000',
|
|
244
|
+
shadowOffset: { width: 0, height: 2 },
|
|
245
|
+
shadowOpacity: 0.35,
|
|
246
|
+
shadowRadius: 4,
|
|
247
|
+
},
|
|
248
|
+
floatingButtonText: {
|
|
249
|
+
color: '#FFF',
|
|
250
|
+
fontWeight: '700',
|
|
251
|
+
fontSize: 13,
|
|
252
|
+
},
|
|
196
253
|
recordingOverlay: {
|
|
197
254
|
position: 'absolute',
|
|
198
255
|
top: 50,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-iinstall",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "🎯 IInstall React Native SDK - The ultimate beta testing & QA feedback tool. Shake-to-report with voice recordings, screen recordings, and screenshots. Zero-config setup with TypeScript support. Perfect for beta testing, QA teams, and user feedback collection.",
|
|
5
5
|
"author": "TesterFlow Team",
|
|
6
6
|
"license": "MIT",
|
package/src/IInstallWrapper.tsx
CHANGED
|
@@ -1,178 +1,40 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { View, StyleSheet, TouchableOpacity, Text, Platform, Alert } from 'react-native';
|
|
3
|
-
// Import from the local SDK files instead of the package
|
|
1
|
+
import React from 'react';
|
|
4
2
|
import { IInstall } from './index';
|
|
5
|
-
import { ShakeDetector } from './ShakeDetector';
|
|
6
3
|
|
|
7
4
|
interface IInstallWrapperProps {
|
|
8
5
|
apiKey: string;
|
|
9
6
|
apiEndpoint?: string;
|
|
10
7
|
children?: React.ReactNode;
|
|
11
8
|
enabled?: boolean;
|
|
9
|
+
// Backward compatibility only. No longer used.
|
|
12
10
|
showDebugButton?: boolean;
|
|
11
|
+
showFloatingButtonOnEmulator?: boolean;
|
|
12
|
+
floatingButtonLabel?: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Platform.OS === 'ios' &&
|
|
22
|
-
(Platform.isPad || Platform.isTV || isDev)
|
|
23
|
-
) || (
|
|
24
|
-
Platform.OS === 'android' && isDev
|
|
25
|
-
);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Helper to detect if native sensors are available
|
|
29
|
-
const hasNativeSensors = () => {
|
|
30
|
-
try {
|
|
31
|
-
// Check if we're in a simulator/emulator environment
|
|
32
|
-
if (isSimulator()) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Additional check for Android emulators
|
|
37
|
-
if (Platform.OS === 'android') {
|
|
38
|
-
// Android emulators typically don't have motion sensors
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// iOS simulators don't have motion sensors
|
|
43
|
-
if (Platform.OS === 'ios' && Platform.isPad) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return true;
|
|
48
|
-
} catch (e) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export const IInstallWrapper: React.FC<IInstallWrapperProps> = ({
|
|
54
|
-
apiKey,
|
|
55
|
-
apiEndpoint = 'https://iinstall.app',
|
|
15
|
+
// Backward-compatible wrapper. The core IInstall component now handles:
|
|
16
|
+
// - shake gesture on real devices
|
|
17
|
+
// - floating manual trigger button on simulator/emulator
|
|
18
|
+
export const IInstallWrapper: React.FC<IInstallWrapperProps> = ({
|
|
19
|
+
apiKey,
|
|
20
|
+
apiEndpoint = 'https://iinstall.app',
|
|
56
21
|
children,
|
|
57
22
|
enabled = true,
|
|
58
|
-
showDebugButton =
|
|
23
|
+
showDebugButton: _showDebugButton = false,
|
|
24
|
+
showFloatingButtonOnEmulator = true,
|
|
25
|
+
floatingButtonLabel = 'Report Issue',
|
|
59
26
|
}) => {
|
|
60
|
-
const shakeDetectorRef = useRef<ShakeDetector | null>(null);
|
|
61
|
-
|
|
62
|
-
// Calculate sensor availability once to avoid state updates
|
|
63
|
-
const sensorsAvailable = enabled && hasNativeSensors();
|
|
64
|
-
const showManualButton = !sensorsAvailable && enabled;
|
|
65
|
-
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
if (!enabled) return;
|
|
68
|
-
|
|
69
|
-
// Only use shake detector if sensors are available
|
|
70
|
-
if (sensorsAvailable) {
|
|
71
|
-
shakeDetectorRef.current = new ShakeDetector(() => {
|
|
72
|
-
// This will trigger the IInstall modal
|
|
73
|
-
// The shake detector will handle the screenshot capture
|
|
74
|
-
});
|
|
75
|
-
shakeDetectorRef.current.start();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return () => {
|
|
79
|
-
shakeDetectorRef.current?.stop();
|
|
80
|
-
};
|
|
81
|
-
}, [enabled, sensorsAvailable]);
|
|
82
|
-
|
|
83
|
-
const handleManualReport = () => {
|
|
84
|
-
// Manually trigger the IInstall feedback modal
|
|
85
|
-
// This simulates a shake gesture
|
|
86
|
-
if (enabled) {
|
|
87
|
-
// The IInstall component will handle the screenshot and modal
|
|
88
|
-
// We can trigger it by simulating the shake event
|
|
89
|
-
console.log('[IInstall] Manual report triggered');
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const handleDebugInfo = () => {
|
|
94
|
-
const debugMessage = [
|
|
95
|
-
`Platform: ${Platform.OS}`,
|
|
96
|
-
`Is Simulator: ${isSimulator() ? 'Yes' : 'No'}`,
|
|
97
|
-
`Native Sensors: ${sensorsAvailable ? 'Available' : 'Not Available'}`,
|
|
98
|
-
`Manual Button: ${showManualButton ? 'Visible' : 'Hidden'}`,
|
|
99
|
-
`Shake Detection: ${sensorsAvailable ? 'Enabled' : 'Disabled'}`,
|
|
100
|
-
].join('\n');
|
|
101
|
-
|
|
102
|
-
Alert.alert('IInstall Debug Info', debugMessage, [{ text: 'OK' }]);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
27
|
return (
|
|
106
|
-
<IInstall
|
|
107
|
-
apiKey={apiKey}
|
|
108
|
-
apiEndpoint={apiEndpoint}
|
|
28
|
+
<IInstall
|
|
29
|
+
apiKey={apiKey}
|
|
30
|
+
apiEndpoint={apiEndpoint}
|
|
109
31
|
enabled={enabled}
|
|
32
|
+
showFloatingButtonOnEmulator={showFloatingButtonOnEmulator}
|
|
33
|
+
floatingButtonLabel={floatingButtonLabel}
|
|
110
34
|
>
|
|
111
35
|
{children}
|
|
112
|
-
|
|
113
|
-
{showManualButton && (
|
|
114
|
-
<View style={styles.manualButtonContainer}>
|
|
115
|
-
<TouchableOpacity
|
|
116
|
-
style={styles.manualButton}
|
|
117
|
-
onPress={handleManualReport}
|
|
118
|
-
>
|
|
119
|
-
<Text style={styles.manualButtonText}>🐛 Report Issue</Text>
|
|
120
|
-
</TouchableOpacity>
|
|
121
|
-
|
|
122
|
-
{showDebugButton && (
|
|
123
|
-
<TouchableOpacity
|
|
124
|
-
style={styles.debugButton}
|
|
125
|
-
onPress={handleDebugInfo}
|
|
126
|
-
>
|
|
127
|
-
<Text style={styles.debugButtonText}>ℹ️</Text>
|
|
128
|
-
</TouchableOpacity>
|
|
129
|
-
)}
|
|
130
|
-
</View>
|
|
131
|
-
)}
|
|
132
36
|
</IInstall>
|
|
133
37
|
);
|
|
134
38
|
};
|
|
135
39
|
|
|
136
|
-
|
|
137
|
-
manualButtonContainer: {
|
|
138
|
-
position: 'absolute',
|
|
139
|
-
bottom: 40,
|
|
140
|
-
right: 20,
|
|
141
|
-
flexDirection: 'row',
|
|
142
|
-
alignItems: 'center',
|
|
143
|
-
},
|
|
144
|
-
manualButton: {
|
|
145
|
-
backgroundColor: '#FF6B6B',
|
|
146
|
-
paddingHorizontal: 16,
|
|
147
|
-
paddingVertical: 12,
|
|
148
|
-
borderRadius: 25,
|
|
149
|
-
elevation: 5,
|
|
150
|
-
shadowColor: '#000',
|
|
151
|
-
shadowOffset: { width: 0, height: 2 },
|
|
152
|
-
shadowOpacity: 0.2,
|
|
153
|
-
shadowRadius: 4,
|
|
154
|
-
marginRight: 10,
|
|
155
|
-
},
|
|
156
|
-
manualButtonText: {
|
|
157
|
-
color: 'white',
|
|
158
|
-
fontSize: 14,
|
|
159
|
-
fontWeight: '600',
|
|
160
|
-
},
|
|
161
|
-
debugButton: {
|
|
162
|
-
backgroundColor: '#4ECDC4',
|
|
163
|
-
width: 40,
|
|
164
|
-
height: 40,
|
|
165
|
-
borderRadius: 20,
|
|
166
|
-
justifyContent: 'center',
|
|
167
|
-
alignItems: 'center',
|
|
168
|
-
elevation: 5,
|
|
169
|
-
shadowColor: '#000',
|
|
170
|
-
shadowOffset: { width: 0, height: 2 },
|
|
171
|
-
shadowOpacity: 0.2,
|
|
172
|
-
shadowRadius: 4,
|
|
173
|
-
},
|
|
174
|
-
debugButtonText: {
|
|
175
|
-
color: 'white',
|
|
176
|
-
fontSize: 16,
|
|
177
|
-
},
|
|
178
|
-
});
|
|
40
|
+
export default IInstallWrapper;
|
package/src/index.tsx
CHANGED
|
@@ -4,24 +4,30 @@ import { captureScreen } from 'react-native-view-shot';
|
|
|
4
4
|
import { ShakeDetector } from './ShakeDetector';
|
|
5
5
|
import { FeedbackModal } from './FeedbackModal';
|
|
6
6
|
import RecordScreen from 'react-native-record-screen';
|
|
7
|
+
import DeviceInfo from 'react-native-device-info';
|
|
7
8
|
|
|
8
9
|
interface IInstallProps {
|
|
9
10
|
apiKey: string;
|
|
10
11
|
apiEndpoint?: string; // e.g. https://iinstall.app
|
|
11
12
|
children?: React.ReactNode;
|
|
12
13
|
enabled?: boolean;
|
|
14
|
+
showFloatingButtonOnEmulator?: boolean;
|
|
15
|
+
floatingButtonLabel?: string;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
export const IInstall: React.FC<IInstallProps> = ({
|
|
16
19
|
apiKey,
|
|
17
20
|
apiEndpoint = 'https://iinstall.app',
|
|
18
21
|
children,
|
|
19
|
-
enabled = true
|
|
22
|
+
enabled = true,
|
|
23
|
+
showFloatingButtonOnEmulator = true,
|
|
24
|
+
floatingButtonLabel = 'Report Issue',
|
|
20
25
|
}) => {
|
|
21
26
|
const [modalVisible, setModalVisible] = useState(false);
|
|
22
27
|
const [screenshotUri, setScreenshotUri] = useState<string | null>(null);
|
|
23
28
|
const [videoUri, setVideoUri] = useState<string | null>(null);
|
|
24
29
|
const [isRecording, setIsRecording] = useState(false);
|
|
30
|
+
const [isEmulator, setIsEmulator] = useState(false);
|
|
25
31
|
|
|
26
32
|
const shakeDetectorRef = useRef<ShakeDetector | null>(null);
|
|
27
33
|
|
|
@@ -34,6 +40,26 @@ export const IInstall: React.FC<IInstallProps> = ({
|
|
|
34
40
|
modalVisibleRef.current = modalVisible;
|
|
35
41
|
}, [isRecording, modalVisible]);
|
|
36
42
|
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
let mounted = true;
|
|
45
|
+
|
|
46
|
+
DeviceInfo.isEmulator()
|
|
47
|
+
.then((value) => {
|
|
48
|
+
if (mounted) {
|
|
49
|
+
setIsEmulator(value);
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.catch(() => {
|
|
53
|
+
if (mounted) {
|
|
54
|
+
setIsEmulator(false);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return () => {
|
|
59
|
+
mounted = false;
|
|
60
|
+
};
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
37
63
|
const handleShake = async () => {
|
|
38
64
|
if (modalVisibleRef.current || isRecordingRef.current) return;
|
|
39
65
|
|
|
@@ -59,7 +85,7 @@ export const IInstall: React.FC<IInstallProps> = ({
|
|
|
59
85
|
});
|
|
60
86
|
|
|
61
87
|
useEffect(() => {
|
|
62
|
-
if (!enabled) return;
|
|
88
|
+
if (!enabled || isEmulator) return;
|
|
63
89
|
|
|
64
90
|
// Use a wrapper to call the current handleShake
|
|
65
91
|
shakeDetectorRef.current = new ShakeDetector(() => handleShakeCallback.current());
|
|
@@ -68,7 +94,18 @@ export const IInstall: React.FC<IInstallProps> = ({
|
|
|
68
94
|
return () => {
|
|
69
95
|
shakeDetectorRef.current?.stop();
|
|
70
96
|
};
|
|
71
|
-
}, [enabled]);
|
|
97
|
+
}, [enabled, isEmulator]);
|
|
98
|
+
|
|
99
|
+
const handleFloatingButtonPress = async () => {
|
|
100
|
+
await handleShakeCallback.current();
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const shouldShowFloatingButton =
|
|
104
|
+
enabled &&
|
|
105
|
+
isEmulator &&
|
|
106
|
+
showFloatingButtonOnEmulator &&
|
|
107
|
+
!modalVisible &&
|
|
108
|
+
!isRecording;
|
|
72
109
|
|
|
73
110
|
const normalizeVideoUri = (value: string) => {
|
|
74
111
|
if (Platform.OS === 'ios' && value.startsWith('/')) {
|
|
@@ -181,6 +218,19 @@ export const IInstall: React.FC<IInstallProps> = ({
|
|
|
181
218
|
<View style={StyleSheet.absoluteFill}>
|
|
182
219
|
{children}
|
|
183
220
|
</View>
|
|
221
|
+
|
|
222
|
+
{shouldShowFloatingButton && (
|
|
223
|
+
<View style={styles.floatingButtonContainer} pointerEvents="box-none">
|
|
224
|
+
<TouchableOpacity
|
|
225
|
+
accessibilityRole="button"
|
|
226
|
+
accessibilityLabel="Open feedback menu"
|
|
227
|
+
onPress={handleFloatingButtonPress}
|
|
228
|
+
style={styles.floatingButton}
|
|
229
|
+
>
|
|
230
|
+
<Text style={styles.floatingButtonText}>{floatingButtonLabel}</Text>
|
|
231
|
+
</TouchableOpacity>
|
|
232
|
+
</View>
|
|
233
|
+
)}
|
|
184
234
|
|
|
185
235
|
{isRecording && (
|
|
186
236
|
<SafeAreaView style={styles.recordingOverlay} pointerEvents="box-none">
|
|
@@ -212,6 +262,30 @@ export const IInstall: React.FC<IInstallProps> = ({
|
|
|
212
262
|
};
|
|
213
263
|
|
|
214
264
|
const styles = StyleSheet.create({
|
|
265
|
+
floatingButtonContainer: {
|
|
266
|
+
position: 'absolute',
|
|
267
|
+
right: 16,
|
|
268
|
+
bottom: 28,
|
|
269
|
+
zIndex: 9998,
|
|
270
|
+
elevation: 20,
|
|
271
|
+
},
|
|
272
|
+
floatingButton: {
|
|
273
|
+
backgroundColor: '#0f172a',
|
|
274
|
+
borderColor: '#2563eb',
|
|
275
|
+
borderWidth: 1,
|
|
276
|
+
borderRadius: 24,
|
|
277
|
+
paddingHorizontal: 14,
|
|
278
|
+
paddingVertical: 10,
|
|
279
|
+
shadowColor: '#000',
|
|
280
|
+
shadowOffset: { width: 0, height: 2 },
|
|
281
|
+
shadowOpacity: 0.35,
|
|
282
|
+
shadowRadius: 4,
|
|
283
|
+
},
|
|
284
|
+
floatingButtonText: {
|
|
285
|
+
color: '#FFF',
|
|
286
|
+
fontWeight: '700',
|
|
287
|
+
fontSize: 13,
|
|
288
|
+
},
|
|
215
289
|
recordingOverlay: {
|
|
216
290
|
position: 'absolute',
|
|
217
291
|
top: 50,
|