yaver-feedback-react-native 0.8.4 → 0.8.7

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.
@@ -115,6 +115,7 @@ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorPro
115
115
  const [shakenThisSession, setShakenThisSession] = (0, react_1.useState)(false);
116
116
  const [menuOpen, setMenuOpen] = (0, react_1.useState)(false);
117
117
  const [hostSuppressed] = (0, react_1.useState)(() => isRunningInsideYaverHost());
118
+ const [launching, setLaunching] = (0, react_1.useState)(false);
118
119
  // Load the persisted disable flag once on mount. Until it resolves we
119
120
  // render nothing — a one-frame flash of the icon before hiding would
120
121
  // be worse than a tiny delayed appearance.
@@ -162,6 +163,24 @@ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorPro
162
163
  colorSub.remove();
163
164
  };
164
165
  }, []);
166
+ (0, react_1.useEffect)(() => {
167
+ const launchSub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:reportLaunch', (event) => {
168
+ if (event?.state === 'starting') {
169
+ setLaunching(true);
170
+ return;
171
+ }
172
+ setLaunching(false);
173
+ });
174
+ const reportSub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:startReport', () => setLaunching(false));
175
+ const loginSub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:startLogin', () => setLaunching(false));
176
+ const pickerSub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:startMachinePicker', () => setLaunching(false));
177
+ return () => {
178
+ launchSub.remove();
179
+ reportSub.remove();
180
+ loginSub.remove();
181
+ pickerSub.remove();
182
+ };
183
+ }, []);
165
184
  const panResponder = (0, react_1.useRef)(react_native_1.PanResponder.create({
166
185
  onStartShouldSetPanResponder: () => true,
167
186
  onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 3 || Math.abs(g.dy) > 3,
@@ -195,9 +214,11 @@ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorPro
195
214
  },
196
215
  })).current;
197
216
  const openFeedback = (0, react_1.useCallback)(() => {
217
+ if (launching)
218
+ return;
198
219
  setMenuOpen(false);
199
220
  void YaverFeedback_1.YaverFeedback.startReport();
200
- }, []);
221
+ }, [launching]);
201
222
  const hideForever = (0, react_1.useCallback)(() => {
202
223
  setMenuOpen(false);
203
224
  setUserDisabled(true);
@@ -233,10 +254,14 @@ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorPro
233
254
  didDrag.current = false;
234
255
  return;
235
256
  }
257
+ if (launching)
258
+ return;
236
259
  openFeedback();
237
260
  }} onLongPress={() => {
238
261
  if (didDrag.current)
239
262
  return;
263
+ if (launching)
264
+ return;
240
265
  setMenuOpen((m) => !m);
241
266
  }} delayLongPress={LONG_PRESS_MS} hitSlop={6} accessibilityRole="button" accessibilityLabel="Open Yaver feedback" style={({ pressed }) => [
242
267
  styles.icon,
@@ -247,7 +272,7 @@ const QuickActionIcon = ({ color: colorProp, backgroundColor: backgroundColorPro
247
272
  backgroundColor: presetColors?.backgroundColor ?? backgroundColor,
248
273
  borderColor: presetColors?.borderColor ?? borderColor,
249
274
  shadowColor: presetColors?.shadowColor ?? shadowColor,
250
- opacity: pressed ? 0.85 : 1,
275
+ opacity: launching ? 0.62 : pressed ? 0.85 : 1,
251
276
  },
252
277
  ]}>
253
278
  <react_native_1.Text style={[
@@ -65,6 +65,19 @@ export declare class YaverFeedback {
65
65
  static setPreferredDevice(deviceId: string): Promise<void>;
66
66
  /** Resolve the currently selected remote machine from the authenticated device list. */
67
67
  static getSelectedRemoteDevice(): Promise<import("./auth").RemoteDevice | null>;
68
+ /**
69
+ * Trigger remote device-auth for a CLI runner on the selected agent
70
+ * (codex login --device-auth / claude auth login --console). Returns
71
+ * the session so the host UI can render the verification URL + code.
72
+ *
73
+ * RN UI layer owns the modal (see FeedbackModal's runner sign-in
74
+ * buttons). This method just proxies into P2PClient — no browser
75
+ * launch, no API keys, works through the relay with an SDK token
76
+ * that carries the runner-auth scope.
77
+ */
78
+ static startRunnerBrowserAuth(runner: string): Promise<import('./types').RunnerBrowserAuthSession>;
79
+ static getRunnerBrowserAuthStatus(sessionId: string): Promise<import('./types').RunnerBrowserAuthSession>;
80
+ static cancelRunnerBrowserAuth(sessionId: string): Promise<void>;
68
81
  /**
69
82
  * Sign out: clear cached token + device, tear down the P2P client. The
70
83
  * SDK stays enabled; the next feedback trigger will re-prompt for login.
@@ -35,6 +35,7 @@ let enabled = false;
35
35
  let p2pClient = null;
36
36
  let shakeDetector = null;
37
37
  let p2pAuthToken = null;
38
+ let reportLaunchInFlight = false;
38
39
  /** Ring buffer of captured errors. */
39
40
  let errorBuffer = [];
40
41
  let maxErrors = 5;
@@ -396,6 +397,32 @@ class YaverFeedback {
396
397
  const all = [...devices.owned, ...devices.shared];
397
398
  return all.find((device) => device.deviceId === preferredDeviceId) ?? null;
398
399
  }
400
+ /**
401
+ * Trigger remote device-auth for a CLI runner on the selected agent
402
+ * (codex login --device-auth / claude auth login --console). Returns
403
+ * the session so the host UI can render the verification URL + code.
404
+ *
405
+ * RN UI layer owns the modal (see FeedbackModal's runner sign-in
406
+ * buttons). This method just proxies into P2PClient — no browser
407
+ * launch, no API keys, works through the relay with an SDK token
408
+ * that carries the runner-auth scope.
409
+ */
410
+ static async startRunnerBrowserAuth(runner) {
411
+ if (!p2pClient) {
412
+ throw new Error('Not connected to any agent. Select a machine first.');
413
+ }
414
+ return p2pClient.startRunnerBrowserAuth(runner);
415
+ }
416
+ static async getRunnerBrowserAuthStatus(sessionId) {
417
+ if (!p2pClient)
418
+ throw new Error('Not connected to any agent.');
419
+ return p2pClient.getRunnerBrowserAuthStatus(sessionId);
420
+ }
421
+ static async cancelRunnerBrowserAuth(sessionId) {
422
+ if (!p2pClient)
423
+ return;
424
+ await p2pClient.cancelRunnerBrowserAuth(sessionId);
425
+ }
399
426
  /**
400
427
  * Sign out: clear cached token + device, tear down the P2P client. The
401
428
  * SDK stays enabled; the next feedback trigger will re-prompt for login.
@@ -425,47 +452,64 @@ class YaverFeedback {
425
452
  if (!enabled) {
426
453
  return;
427
454
  }
428
- // If the caller has autoLogin enabled and we have no session yet, show
429
- // the in-SDK login flow instead of a failing discovery + warning spam.
430
- if (!config.authToken) {
431
- if (config.autoLogin !== false) {
432
- await YaverFeedback.hydrateSession();
433
- }
434
- if (!config.authToken) {
435
- YaverFeedback.showLogin();
436
- return;
437
- }
455
+ if (reportLaunchInFlight) {
456
+ return;
438
457
  }
439
- // Auto-discover if no agent URL was provided
440
- if (!config.agentUrl) {
441
- try {
442
- const result = await Discovery_1.YaverDiscovery.discover({
443
- convexUrl: config.convexUrl,
444
- authToken: config.authToken,
445
- preferredDeviceId: config.preferredDeviceId,
446
- });
447
- if (result) {
448
- config.agentUrl = result.url;
449
- await YaverFeedback.rebuildP2PClient(result.url);
458
+ reportLaunchInFlight = true;
459
+ const { DeviceEventEmitter } = require('react-native');
460
+ DeviceEventEmitter.emit('yaverFeedback:reportLaunch', {
461
+ state: 'starting',
462
+ at: Date.now(),
463
+ });
464
+ try {
465
+ // If the caller has autoLogin enabled and we have no session yet, show
466
+ // the in-SDK login flow instead of a failing discovery + warning spam.
467
+ if (!config.authToken) {
468
+ if (config.autoLogin !== false) {
469
+ await YaverFeedback.hydrateSession();
450
470
  }
451
- else if (config.autoLogin !== false && !config.preferredDeviceId) {
452
- // No agent discovered and no device picked yet — prompt the user
453
- // to pick one of their machines (handles the non-LAN case where
454
- // relay discovery requires knowing which deviceId to target).
455
- YaverFeedback.showMachinePicker();
471
+ if (!config.authToken) {
472
+ YaverFeedback.showLogin();
456
473
  return;
457
474
  }
458
- else {
459
- console.warn('[YaverFeedback] No agent found. Check that `yaver serve` is running on the selected machine.');
460
- }
461
475
  }
462
- catch (err) {
463
- console.warn('[YaverFeedback] Auto-discovery failed:', err);
476
+ // Auto-discover if no agent URL was provided
477
+ if (!config.agentUrl) {
478
+ try {
479
+ const result = await Discovery_1.YaverDiscovery.discover({
480
+ convexUrl: config.convexUrl,
481
+ authToken: config.authToken,
482
+ preferredDeviceId: config.preferredDeviceId,
483
+ });
484
+ if (result) {
485
+ config.agentUrl = result.url;
486
+ await YaverFeedback.rebuildP2PClient(result.url);
487
+ }
488
+ else if (config.autoLogin !== false && !config.preferredDeviceId) {
489
+ // No agent discovered and no device picked yet — prompt the user
490
+ // to pick one of their machines (handles the non-LAN case where
491
+ // relay discovery requires knowing which deviceId to target).
492
+ YaverFeedback.showMachinePicker();
493
+ return;
494
+ }
495
+ else {
496
+ console.warn('[YaverFeedback] No agent found. Check that `yaver serve` is running on the selected machine.');
497
+ }
498
+ }
499
+ catch (err) {
500
+ console.warn('[YaverFeedback] Auto-discovery failed:', err);
501
+ }
464
502
  }
503
+ // Emit event that the FeedbackModal listens for
504
+ DeviceEventEmitter.emit('yaverFeedback:startReport');
505
+ }
506
+ finally {
507
+ reportLaunchInFlight = false;
508
+ DeviceEventEmitter.emit('yaverFeedback:reportLaunch', {
509
+ state: 'settled',
510
+ at: Date.now(),
511
+ });
465
512
  }
466
- // Emit event that the FeedbackModal listens for
467
- const { DeviceEventEmitter } = require('react-native');
468
- DeviceEventEmitter.emit('yaverFeedback:startReport');
469
513
  }
470
514
  /** Returns true if the SDK has been initialized. */
471
515
  static isInitialized() {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const auth_1 = require("../auth");
4
+ const mockFetch = jest.fn();
5
+ global.fetch = mockFetch;
6
+ describe('auth device listing', () => {
7
+ beforeEach(() => {
8
+ jest.clearAllMocks();
9
+ mockFetch.mockReset();
10
+ });
11
+ it('keeps guest-shared devices in the shared bucket', async () => {
12
+ mockFetch.mockResolvedValue({
13
+ ok: true,
14
+ json: () => Promise.resolve({
15
+ devices: [
16
+ {
17
+ deviceId: 'own-1',
18
+ name: 'My Mac',
19
+ platform: 'macos',
20
+ isOnline: true,
21
+ isGuest: false,
22
+ quicHost: '10.0.0.10',
23
+ quicPort: 18080,
24
+ lastHeartbeat: 123,
25
+ },
26
+ {
27
+ deviceId: 'guest-1',
28
+ name: 'yaver-test-ephemeral',
29
+ platform: 'linux',
30
+ isOnline: true,
31
+ isGuest: true,
32
+ hostName: 'Kivanc Cakmak',
33
+ hostEmail: 'kivanc.cakmak@icloud.com',
34
+ accessScope: 'shared-scoped',
35
+ quicHost: '157.180.114.179',
36
+ quicPort: 18080,
37
+ lastHeartbeat: 456,
38
+ },
39
+ ],
40
+ }),
41
+ });
42
+ const result = await (0, auth_1.listReachableDevices)('sdk-user-token');
43
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/devices/list'), expect.objectContaining({
44
+ headers: { Authorization: 'Bearer sdk-user-token' },
45
+ }));
46
+ expect(result.owned).toHaveLength(1);
47
+ expect(result.shared).toHaveLength(1);
48
+ expect(result.owned[0].deviceId).toBe('own-1');
49
+ expect(result.shared[0]).toMatchObject({
50
+ deviceId: 'guest-1',
51
+ isGuest: true,
52
+ hostEmail: 'kivanc.cakmak@icloud.com',
53
+ accessScope: 'shared-scoped',
54
+ });
55
+ });
56
+ it('shows shared devices even when the guest owns no machines', async () => {
57
+ mockFetch.mockResolvedValue({
58
+ ok: true,
59
+ json: () => Promise.resolve({
60
+ devices: [
61
+ {
62
+ deviceId: 'guest-only',
63
+ name: 'yaver-test-ephemeral',
64
+ platform: 'linux',
65
+ isOnline: true,
66
+ isGuest: true,
67
+ hostName: 'Kivanc Cakmak',
68
+ hostEmail: 'kivanc.cakmak@icloud.com',
69
+ accessScope: 'shared-scoped',
70
+ quicHost: '157.180.114.179',
71
+ quicPort: 18080,
72
+ lastHeartbeat: 789,
73
+ },
74
+ ],
75
+ }),
76
+ });
77
+ const result = await (0, auth_1.listReachableDevices)('guest-only-token');
78
+ expect(result.owned).toEqual([]);
79
+ expect(result.shared).toHaveLength(1);
80
+ expect(result.shared[0].deviceId).toBe('guest-only');
81
+ });
82
+ });
package/dist/types.d.ts CHANGED
@@ -1,3 +1,79 @@
1
+ /**
2
+ * Remote browser-style sign-in session for a coding-agent CLI on the
3
+ * connected yaver host. Mirrors runnerBrowserAuthSession on the agent
4
+ * Go side. Progression: starting → awaiting_browser (openUrl + code
5
+ * filled) → completed | failed | cancelled.
6
+ */
7
+ export interface RunnerBrowserAuthSession {
8
+ id: string;
9
+ runner: string;
10
+ method: string;
11
+ status: 'starting' | 'awaiting_browser' | 'completed' | 'failed' | 'cancelled';
12
+ openUrl?: string;
13
+ code?: string;
14
+ detail?: string;
15
+ authConfigured?: boolean;
16
+ authSource?: string;
17
+ error?: string;
18
+ startedAt: number;
19
+ updatedAt: number;
20
+ completedAt?: number;
21
+ }
22
+ export interface IncidentEvent {
23
+ id: string;
24
+ timestamp: number;
25
+ severity: 'info' | 'warn' | 'error' | 'fatal';
26
+ category: string;
27
+ code: string;
28
+ source: string;
29
+ title: string;
30
+ userMessage: string;
31
+ technicalInfo?: string;
32
+ suggestedAction?: string;
33
+ operationId?: string;
34
+ deviceId?: string;
35
+ projectPath?: string;
36
+ target?: string;
37
+ logsAvailable: boolean;
38
+ logRefs?: string[];
39
+ correlationId?: string;
40
+ recoverable: boolean;
41
+ metadata?: Record<string, unknown>;
42
+ resolved?: boolean;
43
+ }
44
+ export interface OperationState {
45
+ id: string;
46
+ kind: string;
47
+ status: string;
48
+ phase?: string;
49
+ message?: string;
50
+ progress?: number;
51
+ deviceId?: string;
52
+ projectPath?: string;
53
+ startedAt: number;
54
+ updatedAt: number;
55
+ incidentIds?: string[];
56
+ metadata?: Record<string, unknown>;
57
+ }
58
+ export interface CapabilityTargetReadiness {
59
+ enabled: boolean;
60
+ reasonCode?: string;
61
+ reason?: string;
62
+ suggestedAction?: string;
63
+ notes?: string[];
64
+ }
65
+ export interface CapabilitySnapshot {
66
+ generatedAt: string;
67
+ machine?: Record<string, unknown>;
68
+ infra?: Record<string, unknown>;
69
+ connectivity?: {
70
+ directAvailable?: boolean;
71
+ relayConfigured?: boolean;
72
+ tunnelConfigured?: boolean;
73
+ tailscaleAvailable?: boolean;
74
+ };
75
+ targets: Record<string, CapabilityTargetReadiness>;
76
+ }
1
77
  export interface FeedbackConfig {
2
78
  /** URL of the Yaver agent (e.g. "http://192.168.1.10:18080"). If omitted, auto-discovery is used. */
3
79
  agentUrl?: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.8.4",
4
- "description": "Visual feedback SDK for Yaver bug reports, screen recording, voice annotations, and local-first developer workflows",
3
+ "version": "0.8.7",
4
+ "description": "Visual feedback SDK for Yaver \u2014 bug reports, screen recording, voice annotations, and local-first developer workflows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { DeviceEventEmitter, Modal } from 'react-native';
3
3
  import { YaverLoginScreen } from './LoginScreen';
4
4
  import { YaverMachinePickerScreen } from './MachinePickerScreen';
@@ -26,6 +26,35 @@ export const AuthOverlay: React.FC = () => {
26
26
  const [pickerVisible, setPickerVisible] = useState(false);
27
27
  const [token, setToken] = useState<string | null>(null);
28
28
  const [pendingInviteCode, setPendingInviteCode] = useState<string | null>(null);
29
+ const activeOverlayRef = useRef<'none' | 'login' | 'guest' | 'picker'>('none');
30
+
31
+ const openLogin = useCallback(() => {
32
+ activeOverlayRef.current = 'login';
33
+ setGuestVisible(false);
34
+ setPickerVisible(false);
35
+ setLoginVisible(true);
36
+ }, []);
37
+
38
+ const openGuest = useCallback(() => {
39
+ activeOverlayRef.current = 'guest';
40
+ setLoginVisible(false);
41
+ setPickerVisible(false);
42
+ setGuestVisible(true);
43
+ }, []);
44
+
45
+ const openPicker = useCallback(() => {
46
+ activeOverlayRef.current = 'picker';
47
+ setLoginVisible(false);
48
+ setGuestVisible(false);
49
+ setPickerVisible(true);
50
+ }, []);
51
+
52
+ const closeAll = useCallback(() => {
53
+ activeOverlayRef.current = 'none';
54
+ setLoginVisible(false);
55
+ setGuestVisible(false);
56
+ setPickerVisible(false);
57
+ }, []);
29
58
 
30
59
  useEffect(() => {
31
60
  let mounted = true;
@@ -36,14 +65,18 @@ export const AuthOverlay: React.FC = () => {
36
65
 
37
66
  const loginSub = DeviceEventEmitter.addListener(
38
67
  'yaverFeedback:startLogin',
39
- () => setLoginVisible(true),
68
+ () => {
69
+ if (activeOverlayRef.current !== 'none') return;
70
+ openLogin();
71
+ },
40
72
  );
41
73
  const pickerSub = DeviceEventEmitter.addListener(
42
74
  'yaverFeedback:startMachinePicker',
43
75
  async () => {
76
+ if (activeOverlayRef.current !== 'none') return;
44
77
  const cached = await getToken();
45
78
  if (cached) setToken(cached);
46
- if (cached) setPickerVisible(true);
79
+ if (cached) openPicker();
47
80
  },
48
81
  );
49
82
  return () => {
@@ -51,24 +84,23 @@ export const AuthOverlay: React.FC = () => {
51
84
  loginSub.remove();
52
85
  pickerSub.remove();
53
86
  };
54
- }, []);
87
+ }, [openLogin, openPicker]);
55
88
 
56
89
  const continueAfterAuth = async (newToken: string, inviteCode?: string) => {
57
90
  setToken(newToken);
58
91
  await YaverFeedback.setAuthToken(newToken);
59
92
  const devices = await listReachableDevices(newToken).catch(() => ({ owned: [], shared: [] }));
60
- setLoginVisible(false);
61
93
  const cleanedInviteCode = (inviteCode ?? '').trim().toUpperCase();
62
94
  if (cleanedInviteCode) {
63
95
  setPendingInviteCode(cleanedInviteCode);
64
- setGuestVisible(true);
96
+ openGuest();
65
97
  return;
66
98
  }
67
99
  if (devices.owned.length === 0 && devices.shared.length === 0) {
68
- setGuestVisible(true);
100
+ openGuest();
69
101
  return;
70
102
  }
71
- setPickerVisible(true);
103
+ openPicker();
72
104
  };
73
105
 
74
106
  const handleLoggedIn = async (newToken: string, opts?: { inviteCode?: string }) => {
@@ -77,8 +109,7 @@ export const AuthOverlay: React.FC = () => {
77
109
 
78
110
  const handleDevicePicked = async (device: RemoteDevice) => {
79
111
  await YaverFeedback.setPreferredDevice(device.deviceId);
80
- setPickerVisible(false);
81
- setGuestVisible(false);
112
+ closeAll();
82
113
  // Continue straight into the feedback flow the user originally triggered.
83
114
  DeviceEventEmitter.emit('yaverFeedback:startReport');
84
115
  };
@@ -89,11 +120,11 @@ export const AuthOverlay: React.FC = () => {
89
120
  visible={loginVisible}
90
121
  animationType="slide"
91
122
  presentationStyle="fullScreen"
92
- onRequestClose={() => setLoginVisible(false)}
123
+ onRequestClose={closeAll}
93
124
  >
94
125
  <YaverLoginScreen
95
126
  onLoggedIn={handleLoggedIn}
96
- onCancel={() => setLoginVisible(false)}
127
+ onCancel={closeAll}
97
128
  initialInviteCode={pendingInviteCode ?? YaverFeedback.getConfig()?.guestInviteCode}
98
129
  />
99
130
  </Modal>
@@ -102,14 +133,14 @@ export const AuthOverlay: React.FC = () => {
102
133
  visible={pickerVisible && !!token}
103
134
  animationType="slide"
104
135
  presentationStyle="fullScreen"
105
- onRequestClose={() => setPickerVisible(false)}
136
+ onRequestClose={closeAll}
106
137
  >
107
138
  {token && (
108
139
  <YaverMachinePickerScreen
109
140
  token={token}
110
141
  currentDeviceId={YaverFeedback.getConfig()?.preferredDeviceId}
111
142
  onPick={handleDevicePicked}
112
- onCancel={() => setPickerVisible(false)}
143
+ onCancel={closeAll}
113
144
  />
114
145
  )}
115
146
  </Modal>
@@ -118,21 +149,19 @@ export const AuthOverlay: React.FC = () => {
118
149
  visible={guestVisible && !!token}
119
150
  animationType="slide"
120
151
  presentationStyle="fullScreen"
121
- onRequestClose={() => setGuestVisible(false)}
152
+ onRequestClose={closeAll}
122
153
  >
123
154
  {token && (
124
155
  <YaverGuestOnboardingScreen
125
156
  token={token}
126
157
  initialInviteCode={pendingInviteCode ?? YaverFeedback.getConfig()?.guestInviteCode}
127
158
  onContinue={() => {
128
- setGuestVisible(false);
129
159
  setPendingInviteCode(null);
130
- setPickerVisible(true);
160
+ openPicker();
131
161
  }}
132
162
  onCancel={() => {
133
- setGuestVisible(false);
134
163
  setPendingInviteCode(null);
135
- setPickerVisible(true);
164
+ openPicker();
136
165
  }}
137
166
  />
138
167
  )}