react-native-iinstall 0.2.10 → 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 +12 -0
- package/lib/IInstallWrapper.js +18 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +64 -4
- package/package.json +1 -1
- package/src/IInstallWrapper.tsx +40 -0
- package/src/index.tsx +80 -3
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface IInstallWrapperProps {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
apiEndpoint?: string;
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
showDebugButton?: boolean;
|
|
8
|
+
showFloatingButtonOnEmulator?: boolean;
|
|
9
|
+
floatingButtonLabel?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const IInstallWrapper: React.FC<IInstallWrapperProps>;
|
|
12
|
+
export default IInstallWrapper;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.IInstallWrapper = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const index_1 = require("./index");
|
|
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}>
|
|
14
|
+
{children}
|
|
15
|
+
</index_1.IInstall>);
|
|
16
|
+
};
|
|
17
|
+
exports.IInstallWrapper = IInstallWrapper;
|
|
18
|
+
exports.default = exports.IInstallWrapper;
|
package/lib/index.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ interface IInstallProps {
|
|
|
4
4
|
apiEndpoint?: string;
|
|
5
5
|
children?: React.ReactNode;
|
|
6
6
|
enabled?: boolean;
|
|
7
|
+
showFloatingButtonOnEmulator?: boolean;
|
|
8
|
+
floatingButtonLabel?: string;
|
|
7
9
|
}
|
|
8
10
|
export declare const IInstall: React.FC<IInstallProps>;
|
|
9
11
|
export default IInstall;
|
|
12
|
+
export { IInstallWrapper } from './IInstallWrapper';
|
package/lib/index.js
CHANGED
|
@@ -36,18 +36,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.IInstall = void 0;
|
|
39
|
+
exports.IInstallWrapper = exports.IInstall = void 0;
|
|
40
40
|
const react_1 = __importStar(require("react"));
|
|
41
41
|
const react_native_1 = require("react-native");
|
|
42
42
|
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,
|
|
@@ -232,3 +289,6 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
232
289
|
}
|
|
233
290
|
});
|
|
234
291
|
exports.default = exports.IInstall;
|
|
292
|
+
// Export the wrapper component for simulator/emulator support
|
|
293
|
+
var IInstallWrapper_1 = require("./IInstallWrapper");
|
|
294
|
+
Object.defineProperty(exports, "IInstallWrapper", { enumerable: true, get: function () { return IInstallWrapper_1.IInstallWrapper; } });
|
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",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { IInstall } from './index';
|
|
3
|
+
|
|
4
|
+
interface IInstallWrapperProps {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
apiEndpoint?: string;
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
// Backward compatibility only. No longer used.
|
|
10
|
+
showDebugButton?: boolean;
|
|
11
|
+
showFloatingButtonOnEmulator?: boolean;
|
|
12
|
+
floatingButtonLabel?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
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',
|
|
21
|
+
children,
|
|
22
|
+
enabled = true,
|
|
23
|
+
showDebugButton: _showDebugButton = false,
|
|
24
|
+
showFloatingButtonOnEmulator = true,
|
|
25
|
+
floatingButtonLabel = 'Report Issue',
|
|
26
|
+
}) => {
|
|
27
|
+
return (
|
|
28
|
+
<IInstall
|
|
29
|
+
apiKey={apiKey}
|
|
30
|
+
apiEndpoint={apiEndpoint}
|
|
31
|
+
enabled={enabled}
|
|
32
|
+
showFloatingButtonOnEmulator={showFloatingButtonOnEmulator}
|
|
33
|
+
floatingButtonLabel={floatingButtonLabel}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</IInstall>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
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,
|
|
@@ -252,3 +326,6 @@ const styles = StyleSheet.create({
|
|
|
252
326
|
});
|
|
253
327
|
|
|
254
328
|
export default IInstall;
|
|
329
|
+
|
|
330
|
+
// Export the wrapper component for simulator/emulator support
|
|
331
|
+
export { IInstallWrapper } from './IInstallWrapper';
|