yaver-feedback-react-native 0.7.17 → 0.8.1

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,324 @@
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
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.QuickActionIcon = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const YaverFeedback_1 = require("./YaverFeedback");
40
+ const preferences_1 = require("./preferences");
41
+ // Mirror the suppression rule used by YaverFeedback + ShakeDetector:
42
+ // when loaded through Yaver's super-host Hermes bundle, the host owns
43
+ // shake + reload UX. We must not render a second action surface.
44
+ function isRunningInsideYaverHost() {
45
+ try {
46
+ return !!react_native_1.NativeModules?.YaverInfo;
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ }
52
+ const DEFAULT_SIZE = 44;
53
+ const DEFAULT_BACKGROUND_COLOR = '#ff6b2c';
54
+ const DEFAULT_LABEL_COLOR = '#111111';
55
+ const DEFAULT_BORDER_COLOR = 'rgba(255,255,255,0.92)';
56
+ const DEFAULT_SHADOW_COLOR = '#000000';
57
+ const LONG_PRESS_MS = 550;
58
+ /**
59
+ * Small tap-to-open icon for the Yaver Feedback SDK.
60
+ *
61
+ * Default UX:
62
+ * - **Tap** opens the feedback modal (same as shake).
63
+ * - **Long-press** (~550ms) opens a menu with "Open feedback" and
64
+ * "Hide icon". Hiding is persisted to AsyncStorage so the user's
65
+ * decision survives app relaunches.
66
+ * - **Drag** repositions the icon.
67
+ *
68
+ * Shake always keeps working independently — even when the icon is
69
+ * hidden the user can still shake to open feedback.
70
+ *
71
+ * Visibility is controlled by `FeedbackConfig.quickIcon`:
72
+ * - `'auto'` (default) → `'after-shake'` on iOS/Android, `'off'` on web.
73
+ * - `'always'` → visible from first render.
74
+ * - `'after-shake'` → hidden until `yaverFeedback:firstShake` fires.
75
+ * - `'off'` → never rendered.
76
+ *
77
+ * Suppressed entirely when the SDK is loaded inside Yaver's super-host
78
+ * (the Yaver mobile app owns the shake gesture + overlay in that case).
79
+ */
80
+ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorProp, foregroundColor: foregroundColorProp, borderColor: borderColorProp, shadowColor: shadowColorProp, initialPosition: initialPositionProp, size = DEFAULT_SIZE, }) => {
81
+ const config = YaverFeedback_1.YaverFeedback.getConfig();
82
+ const mode = (() => {
83
+ const raw = config?.quickIcon ?? 'auto';
84
+ if (raw === 'auto') {
85
+ return react_native_1.Platform.OS === 'web' ? 'off' : 'after-shake';
86
+ }
87
+ return raw;
88
+ })();
89
+ const backgroundColor = backgroundColorProp ??
90
+ colorProp ??
91
+ config?.quickIconBackgroundColor ??
92
+ config?.quickIconColor ??
93
+ DEFAULT_BACKGROUND_COLOR;
94
+ const foregroundColor = foregroundColorProp ??
95
+ config?.quickIconForegroundColor ??
96
+ DEFAULT_LABEL_COLOR;
97
+ const borderColor = borderColorProp ??
98
+ config?.quickIconBorderColor ??
99
+ DEFAULT_BORDER_COLOR;
100
+ const shadowColor = shadowColorProp ??
101
+ config?.quickIconShadowColor ??
102
+ DEFAULT_SHADOW_COLOR;
103
+ const { width, height } = react_native_1.Dimensions.get('window');
104
+ const defaultStart = initialPositionProp ??
105
+ config?.quickIconInitialPosition ?? {
106
+ x: Math.max(width - size - 14, 0),
107
+ y: Math.max(Math.floor(height * 0.35), 80),
108
+ };
109
+ const pan = (0, react_1.useRef)(new react_native_1.Animated.ValueXY(defaultStart)).current;
110
+ const lastPos = (0, react_1.useRef)(defaultStart);
111
+ const dragStart = (0, react_1.useRef)(null);
112
+ const didDrag = (0, react_1.useRef)(false);
113
+ const [userDisabled, setUserDisabled] = (0, react_1.useState)(null);
114
+ const [shakenThisSession, setShakenThisSession] = (0, react_1.useState)(false);
115
+ const [menuOpen, setMenuOpen] = (0, react_1.useState)(false);
116
+ const [hostSuppressed] = (0, react_1.useState)(() => isRunningInsideYaverHost());
117
+ // Load the persisted disable flag once on mount. Until it resolves we
118
+ // render nothing — a one-frame flash of the icon before hiding would
119
+ // be worse than a tiny delayed appearance.
120
+ (0, react_1.useEffect)(() => {
121
+ let alive = true;
122
+ (0, preferences_1.getQuickIconDisabled)().then((v) => {
123
+ if (alive)
124
+ setUserDisabled(v);
125
+ });
126
+ return () => {
127
+ alive = false;
128
+ };
129
+ }, []);
130
+ // `after-shake` mode waits for the first shake before revealing
131
+ // itself. YaverFeedback emits this event from its shake callback.
132
+ (0, react_1.useEffect)(() => {
133
+ const sub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:firstShake', () => setShakenThisSession(true));
134
+ return () => sub.remove();
135
+ }, []);
136
+ // Programmatic control: host apps can call
137
+ // `YaverFeedback.setQuickIconVisible(true)` to re-surface the icon
138
+ // after the user hid it (e.g. from a settings screen).
139
+ (0, react_1.useEffect)(() => {
140
+ const showSub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:quickIconShow', () => {
141
+ setUserDisabled(false);
142
+ void (0, preferences_1.setQuickIconDisabled)(false);
143
+ });
144
+ const hideSub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:quickIconHide', () => {
145
+ setUserDisabled(true);
146
+ void (0, preferences_1.setQuickIconDisabled)(true);
147
+ setMenuOpen(false);
148
+ });
149
+ return () => {
150
+ showSub.remove();
151
+ hideSub.remove();
152
+ };
153
+ }, []);
154
+ const panResponder = (0, react_1.useRef)(react_native_1.PanResponder.create({
155
+ onStartShouldSetPanResponder: () => true,
156
+ onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 3 || Math.abs(g.dy) > 3,
157
+ onPanResponderGrant: () => {
158
+ didDrag.current = false;
159
+ dragStart.current = { ...lastPos.current };
160
+ pan.setOffset({ x: lastPos.current.x, y: lastPos.current.y });
161
+ pan.setValue({ x: 0, y: 0 });
162
+ },
163
+ onPanResponderMove: (_, g) => {
164
+ if (Math.abs(g.dx) > 3 || Math.abs(g.dy) > 3) {
165
+ didDrag.current = true;
166
+ }
167
+ react_native_1.Animated.event([null, { dx: pan.x, dy: pan.y }], {
168
+ useNativeDriver: false,
169
+ })(_, g);
170
+ },
171
+ onPanResponderRelease: (_, g) => {
172
+ pan.flattenOffset();
173
+ const start = dragStart.current ?? lastPos.current;
174
+ const maxX = Math.max(width - size, 0);
175
+ const maxY = Math.max(height - size, 0);
176
+ const nextX = Math.max(0, Math.min(maxX, start.x + g.dx));
177
+ const nextY = Math.max(0, Math.min(maxY, start.y + g.dy));
178
+ lastPos.current = { x: nextX, y: nextY };
179
+ react_native_1.Animated.spring(pan, {
180
+ toValue: { x: nextX, y: nextY },
181
+ useNativeDriver: false,
182
+ friction: 7,
183
+ }).start();
184
+ },
185
+ })).current;
186
+ const openFeedback = (0, react_1.useCallback)(() => {
187
+ setMenuOpen(false);
188
+ void YaverFeedback_1.YaverFeedback.startReport();
189
+ }, []);
190
+ const hideForever = (0, react_1.useCallback)(() => {
191
+ setMenuOpen(false);
192
+ setUserDisabled(true);
193
+ void (0, preferences_1.setQuickIconDisabled)(true);
194
+ }, []);
195
+ if (hostSuppressed)
196
+ return null;
197
+ if (mode === 'off')
198
+ return null;
199
+ if (userDisabled === null)
200
+ return null;
201
+ if (userDisabled)
202
+ return null;
203
+ if (mode === 'after-shake' && !shakenThisSession)
204
+ return null;
205
+ if (!YaverFeedback_1.YaverFeedback.isEnabled())
206
+ return null;
207
+ const visualSize = size;
208
+ const radius = visualSize / 2;
209
+ return (<react_native_1.Animated.View pointerEvents="box-none" style={[
210
+ react_native_1.StyleSheet.absoluteFill,
211
+ { zIndex: 9998 },
212
+ ]}>
213
+ <react_native_1.Animated.View {...panResponder.panHandlers} style={[
214
+ styles.container,
215
+ {
216
+ transform: [{ translateX: pan.x }, { translateY: pan.y }],
217
+ },
218
+ ]}>
219
+ <react_native_1.Pressable onPress={() => {
220
+ if (didDrag.current) {
221
+ didDrag.current = false;
222
+ return;
223
+ }
224
+ openFeedback();
225
+ }} onLongPress={() => {
226
+ if (didDrag.current)
227
+ return;
228
+ setMenuOpen((m) => !m);
229
+ }} delayLongPress={LONG_PRESS_MS} hitSlop={6} accessibilityRole="button" accessibilityLabel="Open Yaver feedback" style={({ pressed }) => [
230
+ styles.icon,
231
+ {
232
+ width: visualSize,
233
+ height: visualSize,
234
+ borderRadius: radius,
235
+ backgroundColor,
236
+ borderColor,
237
+ shadowColor,
238
+ opacity: pressed ? 0.85 : 1,
239
+ },
240
+ ]}>
241
+ <react_native_1.Text style={[
242
+ styles.iconLabel,
243
+ {
244
+ color: foregroundColor,
245
+ fontSize: Math.round(visualSize * 0.5),
246
+ },
247
+ ]}>
248
+ y
249
+ </react_native_1.Text>
250
+ </react_native_1.Pressable>
251
+ {menuOpen ? (<react_native_1.View style={styles.menu}>
252
+ <react_native_1.Pressable onPress={openFeedback} style={({ pressed }) => [
253
+ styles.menuItem,
254
+ pressed && styles.menuItemPressed,
255
+ ]}>
256
+ <react_native_1.Text style={styles.menuItemText}>Open feedback</react_native_1.Text>
257
+ </react_native_1.Pressable>
258
+ <react_native_1.View style={styles.menuDivider}/>
259
+ <react_native_1.Pressable onPress={hideForever} style={({ pressed }) => [
260
+ styles.menuItem,
261
+ pressed && styles.menuItemPressed,
262
+ ]}>
263
+ <react_native_1.Text style={[styles.menuItemText, styles.menuItemDanger]}>
264
+ Hide icon
265
+ </react_native_1.Text>
266
+ </react_native_1.Pressable>
267
+ </react_native_1.View>) : null}
268
+ </react_native_1.Animated.View>
269
+ </react_native_1.Animated.View>);
270
+ };
271
+ exports.QuickActionIcon = QuickActionIcon;
272
+ const styles = react_native_1.StyleSheet.create({
273
+ container: {
274
+ position: 'absolute',
275
+ top: 0,
276
+ left: 0,
277
+ alignItems: 'flex-start',
278
+ },
279
+ icon: {
280
+ alignItems: 'center',
281
+ justifyContent: 'center',
282
+ shadowOffset: { width: 0, height: 2 },
283
+ shadowOpacity: 0.34,
284
+ shadowRadius: 6,
285
+ elevation: 7,
286
+ borderWidth: 2,
287
+ },
288
+ iconLabel: {
289
+ fontWeight: '700',
290
+ includeFontPadding: false,
291
+ },
292
+ menu: {
293
+ marginTop: 6,
294
+ minWidth: 150,
295
+ backgroundColor: '#1f1f23',
296
+ borderRadius: 10,
297
+ paddingVertical: 4,
298
+ shadowColor: '#000',
299
+ shadowOffset: { width: 0, height: 2 },
300
+ shadowOpacity: 0.3,
301
+ shadowRadius: 6,
302
+ elevation: 6,
303
+ },
304
+ menuItem: {
305
+ paddingHorizontal: 14,
306
+ paddingVertical: 10,
307
+ },
308
+ menuItemPressed: {
309
+ backgroundColor: '#2a2a30',
310
+ },
311
+ menuItemText: {
312
+ color: '#f4f4f5',
313
+ fontSize: 14,
314
+ fontWeight: '500',
315
+ },
316
+ menuItemDanger: {
317
+ color: '#f97316',
318
+ },
319
+ menuDivider: {
320
+ height: react_native_1.StyleSheet.hairlineWidth,
321
+ backgroundColor: '#3f3f46',
322
+ marginHorizontal: 8,
323
+ },
324
+ });
@@ -199,6 +199,32 @@ export declare class YaverFeedback {
199
199
  * 3. DevSettings.reload() (Metro dev server fallback)
200
200
  */
201
201
  private static loadBundleAndReload;
202
+ /**
203
+ * Internal: fired from every shake path (dev-menu + accelerometer)
204
+ * before the feedback modal opens. Emits `yaverFeedback:firstShake`
205
+ * exactly once per process so QuickActionIcon's `'after-shake'` mode
206
+ * can surface itself on first shake and stay visible for the rest of
207
+ * the session.
208
+ */
209
+ static notifyShake(): void;
210
+ /**
211
+ * Show / hide the QuickActionIcon programmatically and persist the
212
+ * choice across launches. Host apps can call this from a settings
213
+ * screen so the user has a second way to re-enable the icon after
214
+ * hiding it via the icon's own long-press menu — shake is always the
215
+ * third back-door because it never depends on a visible control.
216
+ */
217
+ static setQuickIconVisible(visible: boolean): Promise<void>;
218
+ /**
219
+ * Returns `true` when the user has chosen to hide the QuickActionIcon
220
+ * (via its long-press menu or `setQuickIconVisible(false)`).
221
+ * FeedbackModal uses this to surface a one-tap "Show quick icon"
222
+ * control so the user can bring the icon back without having to know
223
+ * about the programmatic API.
224
+ */
225
+ static isQuickIconHidden(): Promise<boolean>;
226
+ /** Clear the persisted "user hid the icon" flag. */
227
+ static resetQuickIconPreference(): Promise<void>;
202
228
  /** Tear down the SDK (stop shake detector, clear state). */
203
229
  static destroy(): void;
204
230
  }
@@ -7,6 +7,7 @@ const BlackBox_1 = require("./BlackBox");
7
7
  const P2PClient_1 = require("./P2PClient");
8
8
  const ShakeDetector_1 = require("./ShakeDetector");
9
9
  const auth_1 = require("./auth");
10
+ const preferences_1 = require("./preferences");
10
11
  // Is this JS runtime the Yaver mobile app's super-host bridge? The
11
12
  // YaverInfo native module is only registered inside Yaver's container
12
13
  // (mobile/ios/Yaver/YaverInfo.{swift,m} + Android counterpart); a
@@ -38,6 +39,13 @@ let errorBuffer = [];
38
39
  let maxErrors = 5;
39
40
  /** Track whether BlackBox was running before disable (to restart on enable). */
40
41
  let blackBoxWasStreaming = false;
42
+ /**
43
+ * Tracks whether the user has already shaken once in this process.
44
+ * Consumed by QuickActionIcon's `'after-shake'` mode so the icon
45
+ * appears the first time a user discovers shake and stays around
46
+ * thereafter.
47
+ */
48
+ let firstShakeFired = false;
41
49
  /**
42
50
  * Flag evaluation cache — 30s TTL per `userId|key`. Prevents a
43
51
  * tight render loop from hammering /flags/eval when the dev calls
@@ -68,6 +76,7 @@ class YaverFeedback {
68
76
  autoLogin: true,
69
77
  ...cfg,
70
78
  };
79
+ firstShakeFired = false;
71
80
  // Route the in-SDK login screen to prod yaver.io by default; callers may
72
81
  // override for staging via authConvexSiteUrl / authWebBaseUrl.
73
82
  (0, auth_1.configureAuthEndpoints)({
@@ -118,6 +127,7 @@ class YaverFeedback {
118
127
  if (enabled && config.trigger === 'shake') {
119
128
  shakeDetector = new ShakeDetector_1.ShakeDetector();
120
129
  shakeDetector.start(() => {
130
+ YaverFeedback.notifyShake();
121
131
  if (config?.reportingOnly) {
122
132
  YaverFeedback.sendAutoReport();
123
133
  }
@@ -450,6 +460,7 @@ class YaverFeedback {
450
460
  if (config?.trigger === 'shake' && !shakeDetector) {
451
461
  shakeDetector = new ShakeDetector_1.ShakeDetector();
452
462
  shakeDetector.start(() => {
463
+ YaverFeedback.notifyShake();
453
464
  if (config?.reportingOnly) {
454
465
  YaverFeedback.sendAutoReport();
455
466
  }
@@ -752,12 +763,63 @@ class YaverFeedback {
752
763
  // Not in dev mode
753
764
  }
754
765
  }
766
+ /**
767
+ * Internal: fired from every shake path (dev-menu + accelerometer)
768
+ * before the feedback modal opens. Emits `yaverFeedback:firstShake`
769
+ * exactly once per process so QuickActionIcon's `'after-shake'` mode
770
+ * can surface itself on first shake and stay visible for the rest of
771
+ * the session.
772
+ */
773
+ static notifyShake() {
774
+ if (firstShakeFired)
775
+ return;
776
+ firstShakeFired = true;
777
+ try {
778
+ const { DeviceEventEmitter } = require('react-native');
779
+ DeviceEventEmitter.emit('yaverFeedback:firstShake');
780
+ }
781
+ catch {
782
+ // emitter unavailable (e.g. jsdom unit test) — safe to ignore
783
+ }
784
+ }
785
+ /**
786
+ * Show / hide the QuickActionIcon programmatically and persist the
787
+ * choice across launches. Host apps can call this from a settings
788
+ * screen so the user has a second way to re-enable the icon after
789
+ * hiding it via the icon's own long-press menu — shake is always the
790
+ * third back-door because it never depends on a visible control.
791
+ */
792
+ static async setQuickIconVisible(visible) {
793
+ await (0, preferences_1.setQuickIconDisabled)(!visible);
794
+ try {
795
+ const { DeviceEventEmitter } = require('react-native');
796
+ DeviceEventEmitter.emit(visible ? 'yaverFeedback:quickIconShow' : 'yaverFeedback:quickIconHide');
797
+ }
798
+ catch {
799
+ // emitter unavailable — preference is still persisted
800
+ }
801
+ }
802
+ /**
803
+ * Returns `true` when the user has chosen to hide the QuickActionIcon
804
+ * (via its long-press menu or `setQuickIconVisible(false)`).
805
+ * FeedbackModal uses this to surface a one-tap "Show quick icon"
806
+ * control so the user can bring the icon back without having to know
807
+ * about the programmatic API.
808
+ */
809
+ static async isQuickIconHidden() {
810
+ return (0, preferences_1.getQuickIconDisabled)();
811
+ }
812
+ /** Clear the persisted "user hid the icon" flag. */
813
+ static async resetQuickIconPreference() {
814
+ await YaverFeedback.setQuickIconVisible(true);
815
+ }
755
816
  /** Tear down the SDK (stop shake detector, clear state). */
756
817
  static destroy() {
757
818
  if (shakeDetector) {
758
819
  shakeDetector.stop();
759
820
  shakeDetector = null;
760
821
  }
822
+ firstShakeFired = false;
761
823
  enabled = false;
762
824
  config = null;
763
825
  p2pClient = null;
package/dist/capture.d.ts CHANGED
@@ -22,6 +22,17 @@
22
22
  * modal. See `FeedbackModal.handleScreenshotForFix`.
23
23
  */
24
24
  export declare function captureScreenshot(): Promise<string>;
25
+ export interface PickedFeedbackFile {
26
+ path: string;
27
+ name: string;
28
+ mimeType?: string;
29
+ kind: 'image' | 'video' | 'audio' | 'unknown';
30
+ }
31
+ /**
32
+ * Pick an existing media file from the device. Requires
33
+ * `expo-document-picker` to be installed.
34
+ */
35
+ export declare function pickFeedbackFile(): Promise<PickedFeedbackFile>;
25
36
  /**
26
37
  * Start a screen-recording session. Requires
27
38
  * `react-native-record-screen` as a peer dep.
package/dist/capture.js CHANGED
@@ -16,6 +16,7 @@
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.captureScreenshot = captureScreenshot;
19
+ exports.pickFeedbackFile = pickFeedbackFile;
19
20
  exports.startVideoRecording = startVideoRecording;
20
21
  exports.stopVideoRecording = stopVideoRecording;
21
22
  exports.isVideoRecording = isVideoRecording;
@@ -45,6 +46,64 @@ async function captureScreenshot() {
45
46
  String(err));
46
47
  }
47
48
  }
49
+ function classifyPickedFile(name, mimeType) {
50
+ const lowerName = name.toLowerCase();
51
+ const lowerMime = (mimeType ?? '').toLowerCase();
52
+ if (lowerMime.startsWith('image/') ||
53
+ lowerName.endsWith('.png') ||
54
+ lowerName.endsWith('.jpg') ||
55
+ lowerName.endsWith('.jpeg') ||
56
+ lowerName.endsWith('.webp')) {
57
+ return 'image';
58
+ }
59
+ if (lowerMime.startsWith('video/') ||
60
+ lowerName.endsWith('.mp4') ||
61
+ lowerName.endsWith('.mov') ||
62
+ lowerName.endsWith('.m4v')) {
63
+ return 'video';
64
+ }
65
+ if (lowerMime.startsWith('audio/') ||
66
+ lowerName.endsWith('.m4a') ||
67
+ lowerName.endsWith('.aac') ||
68
+ lowerName.endsWith('.wav') ||
69
+ lowerName.endsWith('.mp3')) {
70
+ return 'audio';
71
+ }
72
+ return 'unknown';
73
+ }
74
+ /**
75
+ * Pick an existing media file from the device. Requires
76
+ * `expo-document-picker` to be installed.
77
+ */
78
+ async function pickFeedbackFile() {
79
+ try {
80
+ const picker = require('expo-document-picker');
81
+ const result = await picker.getDocumentAsync({
82
+ copyToCacheDirectory: true,
83
+ multiple: false,
84
+ type: ['image/*', 'video/*', 'audio/*'],
85
+ });
86
+ if (result?.canceled) {
87
+ throw new Error('File selection canceled.');
88
+ }
89
+ const asset = result?.assets?.[0];
90
+ if (!asset?.uri) {
91
+ throw new Error('No file selected.');
92
+ }
93
+ const name = asset.name || asset.uri.split('/').pop() || 'attachment';
94
+ const mimeType = asset.mimeType;
95
+ return {
96
+ path: asset.uri,
97
+ name,
98
+ mimeType,
99
+ kind: classifyPickedFile(name, mimeType),
100
+ };
101
+ }
102
+ catch (err) {
103
+ throw new Error('[YaverFeedback] File upload needs `expo-document-picker` as an optional peer dependency. ' +
104
+ String(err));
105
+ }
106
+ }
48
107
  let videoRecorderModule = null;
49
108
  let videoRecordingActive = false;
50
109
  /**
package/dist/index.d.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  /**
2
2
  * yaver-feedback-react-native — Visual feedback SDK for Yaver.
3
3
  *
4
- * Shake-to-report surface with five one-tap actions:
4
+ * Shake-to-report surface with four core actions:
5
5
  * 1. Hot Reload — instant JS reload
6
- * 2. Screenshot & Fix — capture the screen under the modal and
7
- * kick a fix task on the agent
8
- * 3. Vibing — open a vibing session on the agent
9
- * 4. Start / Stop Recording screen recording toggle
10
- * 5. Send Video — submit the last recording
6
+ * 2. Vibing — open a vibing session on the agent
7
+ * 3. Screenshot / Upload — capture the screen under the modal or
8
+ * upload existing media
9
+ * 4. Screen Recording start, then stop + upload
10
+ *
11
+ * The small quick-access icon stays hidden until the first shake by
12
+ * default on mobile, then remains available unless the user hides it.
11
13
  *
12
14
  * @example
13
15
  * ```tsx
@@ -44,10 +46,13 @@ export { AuthOverlay } from './AuthOverlay';
44
46
  export { ShakeDetector } from './ShakeDetector';
45
47
  export { FloatingButton } from './FloatingButton';
46
48
  export { FeedbackModal } from './FeedbackModal';
49
+ export { QuickActionIcon } from './QuickActionIcon';
50
+ export type { QuickActionIconProps } from './QuickActionIcon';
47
51
  export { FixReport } from './FixReport';
52
+ export { getQuickIconDisabled, setQuickIconDisabled, clearQuickIconDisabled, } from './preferences';
48
53
  export { configureAuthEndpoints, getConvexSiteUrl, getWebBaseUrl, getToken, saveToken, clearToken, getUser, saveUser, getSelectedDeviceId, saveSelectedDeviceId, clearSelectedDeviceId, validateToken, signInWithApple, signInWithOAuth, signupWithEmail, loginWithEmail, listReachableDevices, DEFAULT_CONVEX_SITE_URL, DEFAULT_WEB_BASE_URL, DEFAULT_OAUTH_REDIRECT, } from './auth';
49
54
  export type { OAuthProvider, User, RemoteDevice, DeviceList, } from './auth';
50
- export { captureScreenshot, startVideoRecording, stopVideoRecording, isVideoRecording, } from './capture';
55
+ export { captureScreenshot, pickFeedbackFile, startVideoRecording, stopVideoRecording, isVideoRecording, } from './capture';
51
56
  export { uploadFeedback } from './upload';
52
57
  export type { FeedbackConfig, FeedbackBundle, FeedbackMetadata, DeviceInfo, AppInfo, TimelineEvent, FeedbackReport, FeedbackStreamEvent, VoiceCapability, CapturedError, TestFix, TestSession, } from './types';
53
58
  export type { BlackBoxEvent, BlackBoxConfig, BlackBoxCommand, CommandHandler } from './BlackBox';
package/dist/index.js CHANGED
@@ -2,13 +2,15 @@
2
2
  /**
3
3
  * yaver-feedback-react-native — Visual feedback SDK for Yaver.
4
4
  *
5
- * Shake-to-report surface with five one-tap actions:
5
+ * Shake-to-report surface with four core actions:
6
6
  * 1. Hot Reload — instant JS reload
7
- * 2. Screenshot & Fix — capture the screen under the modal and
8
- * kick a fix task on the agent
9
- * 3. Vibing — open a vibing session on the agent
10
- * 4. Start / Stop Recording screen recording toggle
11
- * 5. Send Video — submit the last recording
7
+ * 2. Vibing — open a vibing session on the agent
8
+ * 3. Screenshot / Upload — capture the screen under the modal or
9
+ * upload existing media
10
+ * 4. Screen Recording start, then stop + upload
11
+ *
12
+ * The small quick-access icon stays hidden until the first shake by
13
+ * default on mobile, then remains available unless the user hides it.
12
14
  *
13
15
  * @example
14
16
  * ```tsx
@@ -28,7 +30,7 @@
28
30
  * ```
29
31
  */
30
32
  Object.defineProperty(exports, "__esModule", { value: true });
31
- exports.uploadFeedback = exports.isVideoRecording = exports.stopVideoRecording = exports.startVideoRecording = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getSelectedDeviceId = exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = exports.getToken = exports.getWebBaseUrl = exports.getConvexSiteUrl = exports.configureAuthEndpoints = exports.FixReport = exports.FeedbackModal = exports.FloatingButton = exports.ShakeDetector = exports.AuthOverlay = exports.PairDeviceModal = exports.YaverMachinePickerScreen = exports.YaverLoginScreen = exports.YaverConnectionScreen = exports.P2PClient = exports.YaverDiscovery = exports.initExpo = exports.YaverUpdates = exports.BlackBox = exports.YaverFeedback = void 0;
33
+ exports.uploadFeedback = exports.isVideoRecording = exports.stopVideoRecording = exports.startVideoRecording = exports.pickFeedbackFile = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getSelectedDeviceId = exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = exports.getToken = exports.getWebBaseUrl = exports.getConvexSiteUrl = exports.configureAuthEndpoints = exports.clearQuickIconDisabled = exports.setQuickIconDisabled = exports.getQuickIconDisabled = exports.FixReport = exports.QuickActionIcon = exports.FeedbackModal = exports.FloatingButton = exports.ShakeDetector = exports.AuthOverlay = exports.PairDeviceModal = exports.YaverMachinePickerScreen = exports.YaverLoginScreen = exports.YaverConnectionScreen = exports.P2PClient = exports.YaverDiscovery = exports.initExpo = exports.YaverUpdates = exports.BlackBox = exports.YaverFeedback = void 0;
32
34
  var YaverFeedback_1 = require("./YaverFeedback");
33
35
  Object.defineProperty(exports, "YaverFeedback", { enumerable: true, get: function () { return YaverFeedback_1.YaverFeedback; } });
34
36
  var BlackBox_1 = require("./BlackBox");
@@ -57,8 +59,14 @@ var FloatingButton_1 = require("./FloatingButton");
57
59
  Object.defineProperty(exports, "FloatingButton", { enumerable: true, get: function () { return FloatingButton_1.FloatingButton; } });
58
60
  var FeedbackModal_1 = require("./FeedbackModal");
59
61
  Object.defineProperty(exports, "FeedbackModal", { enumerable: true, get: function () { return FeedbackModal_1.FeedbackModal; } });
62
+ var QuickActionIcon_1 = require("./QuickActionIcon");
63
+ Object.defineProperty(exports, "QuickActionIcon", { enumerable: true, get: function () { return QuickActionIcon_1.QuickActionIcon; } });
60
64
  var FixReport_1 = require("./FixReport");
61
65
  Object.defineProperty(exports, "FixReport", { enumerable: true, get: function () { return FixReport_1.FixReport; } });
66
+ var preferences_1 = require("./preferences");
67
+ Object.defineProperty(exports, "getQuickIconDisabled", { enumerable: true, get: function () { return preferences_1.getQuickIconDisabled; } });
68
+ Object.defineProperty(exports, "setQuickIconDisabled", { enumerable: true, get: function () { return preferences_1.setQuickIconDisabled; } });
69
+ Object.defineProperty(exports, "clearQuickIconDisabled", { enumerable: true, get: function () { return preferences_1.clearQuickIconDisabled; } });
62
70
  var auth_1 = require("./auth");
63
71
  Object.defineProperty(exports, "configureAuthEndpoints", { enumerable: true, get: function () { return auth_1.configureAuthEndpoints; } });
64
72
  Object.defineProperty(exports, "getConvexSiteUrl", { enumerable: true, get: function () { return auth_1.getConvexSiteUrl; } });
@@ -82,6 +90,7 @@ Object.defineProperty(exports, "DEFAULT_WEB_BASE_URL", { enumerable: true, get:
82
90
  Object.defineProperty(exports, "DEFAULT_OAUTH_REDIRECT", { enumerable: true, get: function () { return auth_1.DEFAULT_OAUTH_REDIRECT; } });
83
91
  var capture_1 = require("./capture");
84
92
  Object.defineProperty(exports, "captureScreenshot", { enumerable: true, get: function () { return capture_1.captureScreenshot; } });
93
+ Object.defineProperty(exports, "pickFeedbackFile", { enumerable: true, get: function () { return capture_1.pickFeedbackFile; } });
85
94
  Object.defineProperty(exports, "startVideoRecording", { enumerable: true, get: function () { return capture_1.startVideoRecording; } });
86
95
  Object.defineProperty(exports, "stopVideoRecording", { enumerable: true, get: function () { return capture_1.stopVideoRecording; } });
87
96
  Object.defineProperty(exports, "isVideoRecording", { enumerable: true, get: function () { return capture_1.isVideoRecording; } });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * SDK user preferences persisted across launches.
3
+ *
4
+ * Currently only the quick-action icon's user-level dismiss flag
5
+ * lives here: the dev enables the icon via `FeedbackConfig.quickIcon`,
6
+ * but the *user* can long-press → Hide to opt out, and we remember
7
+ * that choice across launches so their next app session still
8
+ * respects it.
9
+ *
10
+ * AsyncStorage is an optional peer dep — if it's not installed the
11
+ * getters return `false` and the setters silently no-op, so the icon
12
+ * still works (it just can't remember the disable beyond the
13
+ * in-memory session).
14
+ */
15
+ /** True if the user has long-pressed the icon and chosen "Hide". */
16
+ export declare function getQuickIconDisabled(): Promise<boolean>;
17
+ export declare function setQuickIconDisabled(disabled: boolean): Promise<void>;
18
+ export declare function clearQuickIconDisabled(): Promise<void>;