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.
@@ -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 IInstall = ({ apiKey, apiEndpoint = 'https://iinstall.app', children, enabled = true }) => {
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.10",
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';