react-native-debug-toolkit 3.2.1 → 3.2.3

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.
Files changed (66) hide show
  1. package/README.md +13 -2
  2. package/README.zh-CN.md +13 -2
  3. package/android/build.gradle +34 -0
  4. package/android/src/main/AndroidManifest.xml +1 -0
  5. package/android/src/main/java/com/reactnativedebugtoolkit/DebugToolkitDevConnectModule.java +154 -0
  6. package/android/src/main/java/com/reactnativedebugtoolkit/ReactNativeDebugToolkitPackage.java +25 -0
  7. package/ios/DebugToolkitDevConnect.mm +81 -0
  8. package/lib/commonjs/features/devConnect/DevConnectQrScanner.js +115 -70
  9. package/lib/commonjs/features/devConnect/DevConnectQrScanner.js.map +1 -1
  10. package/lib/commonjs/features/devConnect/DevConnectTab.js +232 -161
  11. package/lib/commonjs/features/devConnect/DevConnectTab.js.map +1 -1
  12. package/lib/commonjs/features/devConnect/devConnectPreferences.js +35 -5
  13. package/lib/commonjs/features/devConnect/devConnectPreferences.js.map +1 -1
  14. package/lib/commonjs/features/devConnect/devConnectUtils.js +99 -15
  15. package/lib/commonjs/features/devConnect/devConnectUtils.js.map +1 -1
  16. package/lib/commonjs/features/devConnect/index.js +39 -2
  17. package/lib/commonjs/features/devConnect/index.js.map +1 -1
  18. package/lib/commonjs/features/devConnect/nativeDevConnect.js +108 -0
  19. package/lib/commonjs/features/devConnect/nativeDevConnect.js.map +1 -0
  20. package/lib/commonjs/features/devConnect/platformDetect.js +7 -11
  21. package/lib/commonjs/features/devConnect/platformDetect.js.map +1 -1
  22. package/lib/commonjs/utils/debugPreferences.js +43 -6
  23. package/lib/commonjs/utils/debugPreferences.js.map +1 -1
  24. package/lib/module/features/devConnect/DevConnectQrScanner.js +116 -71
  25. package/lib/module/features/devConnect/DevConnectQrScanner.js.map +1 -1
  26. package/lib/module/features/devConnect/DevConnectTab.js +235 -164
  27. package/lib/module/features/devConnect/DevConnectTab.js.map +1 -1
  28. package/lib/module/features/devConnect/devConnectPreferences.js +33 -6
  29. package/lib/module/features/devConnect/devConnectPreferences.js.map +1 -1
  30. package/lib/module/features/devConnect/devConnectUtils.js +94 -15
  31. package/lib/module/features/devConnect/devConnectUtils.js.map +1 -1
  32. package/lib/module/features/devConnect/index.js +11 -3
  33. package/lib/module/features/devConnect/index.js.map +1 -1
  34. package/lib/module/features/devConnect/nativeDevConnect.js +102 -0
  35. package/lib/module/features/devConnect/nativeDevConnect.js.map +1 -0
  36. package/lib/module/features/devConnect/platformDetect.js +8 -12
  37. package/lib/module/features/devConnect/platformDetect.js.map +1 -1
  38. package/lib/module/utils/debugPreferences.js +43 -6
  39. package/lib/module/utils/debugPreferences.js.map +1 -1
  40. package/lib/typescript/src/features/devConnect/DevConnectQrScanner.d.ts +3 -2
  41. package/lib/typescript/src/features/devConnect/DevConnectQrScanner.d.ts.map +1 -1
  42. package/lib/typescript/src/features/devConnect/DevConnectTab.d.ts.map +1 -1
  43. package/lib/typescript/src/features/devConnect/devConnectPreferences.d.ts +6 -0
  44. package/lib/typescript/src/features/devConnect/devConnectPreferences.d.ts.map +1 -1
  45. package/lib/typescript/src/features/devConnect/devConnectUtils.d.ts +18 -1
  46. package/lib/typescript/src/features/devConnect/devConnectUtils.d.ts.map +1 -1
  47. package/lib/typescript/src/features/devConnect/index.d.ts +2 -2
  48. package/lib/typescript/src/features/devConnect/index.d.ts.map +1 -1
  49. package/lib/typescript/src/features/devConnect/nativeDevConnect.d.ts +17 -0
  50. package/lib/typescript/src/features/devConnect/nativeDevConnect.d.ts.map +1 -0
  51. package/lib/typescript/src/features/devConnect/platformDetect.d.ts.map +1 -1
  52. package/lib/typescript/src/features/devConnect/types.d.ts +3 -0
  53. package/lib/typescript/src/features/devConnect/types.d.ts.map +1 -1
  54. package/lib/typescript/src/utils/debugPreferences.d.ts +2 -0
  55. package/lib/typescript/src/utils/debugPreferences.d.ts.map +1 -1
  56. package/package.json +4 -1
  57. package/react-native-debug-toolkit.podspec +18 -0
  58. package/src/features/devConnect/DevConnectQrScanner.tsx +101 -60
  59. package/src/features/devConnect/DevConnectTab.tsx +227 -105
  60. package/src/features/devConnect/devConnectPreferences.ts +50 -5
  61. package/src/features/devConnect/devConnectUtils.ts +122 -15
  62. package/src/features/devConnect/index.ts +13 -0
  63. package/src/features/devConnect/nativeDevConnect.ts +126 -0
  64. package/src/features/devConnect/platformDetect.ts +8 -13
  65. package/src/features/devConnect/types.ts +3 -0
  66. package/src/utils/debugPreferences.ts +49 -4
package/README.md CHANGED
@@ -26,6 +26,15 @@ RN App -> Debug Panel -> local daemon -> Web Console / HTTP API / MCP
26
26
  npm install react-native-debug-toolkit
27
27
  ```
28
28
 
29
+ Install the native part and rebuild the app:
30
+
31
+ ```bash
32
+ cd ios && pod install
33
+ # Android: Gradle autolinking runs on the next build
34
+ ```
35
+
36
+ Expo Go cannot load this native module. Use a development build, prebuild, or bare React Native app.
37
+
29
38
  Optional dependencies:
30
39
 
31
40
  ```bash
@@ -64,9 +73,11 @@ Open the Web Console:
64
73
  http://127.0.0.1:3799/console
65
74
  ```
66
75
 
67
- In the app, open Debug Panel -> `DevConnect` -> `Send Once` or `Start Live Sync`.
76
+ In the app, open Debug Panel -> `DevConnect` -> `Send Once` or `Start Live Sync` for desktop logs.
77
+
78
+ DevConnect auto-detects simulator/emulator and uses local host settings automatically. On real devices, enter your computer IP to connect.
68
79
 
69
- DevConnect auto-detects simulator/emulator and uses `localhost` automatically. On real devices, enter your computer IP to connect.
80
+ For Remote JS Bundle, run Metro on your computer, enter computer IP and Metro port in `DevConnect`, then tap `Use Metro Bundle`. DevConnect writes React Native's native dev-server host setting and reloads the app. The IP and ports are persisted through AsyncStorage when installed, or through the native module after rebuild.
70
81
 
71
82
  QR scan is optional. Install `react-native-camera-kit` or `expo-camera` in the app to enable the scan button. The app must request camera permission before scanning.
72
83
 
package/README.zh-CN.md CHANGED
@@ -26,6 +26,15 @@ RN App -> Debug Panel -> local daemon -> Web Console / HTTP API / MCP
26
26
  npm install react-native-debug-toolkit
27
27
  ```
28
28
 
29
+ 安装原生部分并重新构建 App:
30
+
31
+ ```bash
32
+ cd ios && pod install
33
+ # Android:下次构建时 Gradle autolinking 生效
34
+ ```
35
+
36
+ Expo Go 不能加载这个原生模块。Expo 项目需用 development build、prebuild,或 bare React Native。
37
+
29
38
  可选依赖:
30
39
 
31
40
  ```bash
@@ -64,9 +73,11 @@ npm exec debug-toolkit --daemon-only
64
73
  http://127.0.0.1:3799/console
65
74
  ```
66
75
 
67
- App 内打开 Debug Panel -> `DevConnect` -> `Send Once` 或 `Start Live Sync`。
76
+ App 内打开 Debug Panel -> `DevConnect` -> `Send Once` 或 `Start Live Sync` 同步桌面日志。
77
+
78
+ DevConnect 自动识别模拟器/真机,模拟器下自动使用本机 Metro/daemon 地址。真机需输入电脑 IP 地址。
68
79
 
69
- DevConnect 自动识别模拟器/真机,模拟器下自动使用 `localhost`。真机需输入电脑 IP 地址。
80
+ Remote JS Bundle:先在电脑启动 Metro,在 `DevConnect` 输入电脑 IP 和 Metro 端口,然后点 `Use Metro Bundle`。DevConnect 会写入 React Native 原生 dev-server host 设置并 reload App。IP 和端口会通过 AsyncStorage 持久化;如果没装 AsyncStorage,则在重建后通过本库原生模块持久化。
70
81
 
71
82
  扫码是可选能力。App 安装 `react-native-camera-kit` 或 `expo-camera` 后,DevConnect 才显示扫码按钮。App 仍需自己配置相机权限文案,并在使用扫码前申请相机权限。
72
83
 
@@ -0,0 +1,34 @@
1
+ buildscript {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+ dependencies {
7
+ classpath("com.android.tools.build:gradle:7.4.2")
8
+ }
9
+ }
10
+
11
+ def safeExtGet(prop, fallback) {
12
+ return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
13
+ }
14
+
15
+ apply plugin: 'com.android.library'
16
+
17
+ android {
18
+ namespace 'com.reactnativedebugtoolkit'
19
+ compileSdkVersion safeExtGet('compileSdkVersion', 35)
20
+
21
+ defaultConfig {
22
+ minSdkVersion safeExtGet('minSdkVersion', 23)
23
+ targetSdkVersion safeExtGet('targetSdkVersion', 35)
24
+ }
25
+ }
26
+
27
+ repositories {
28
+ google()
29
+ mavenCentral()
30
+ }
31
+
32
+ dependencies {
33
+ implementation 'com.facebook.react:react-android'
34
+ }
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,154 @@
1
+ package com.reactnativedebugtoolkit;
2
+
3
+ import android.content.Context;
4
+ import android.content.SharedPreferences;
5
+ import android.preference.PreferenceManager;
6
+
7
+ import androidx.annotation.NonNull;
8
+ import androidx.annotation.Nullable;
9
+
10
+ import com.facebook.react.bridge.Arguments;
11
+ import com.facebook.react.bridge.Promise;
12
+ import com.facebook.react.bridge.ReactApplicationContext;
13
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
14
+ import com.facebook.react.bridge.ReactMethod;
15
+ import com.facebook.react.bridge.UiThreadUtil;
16
+ import com.facebook.react.bridge.WritableMap;
17
+
18
+ import java.lang.reflect.Method;
19
+
20
+ public class DebugToolkitDevConnectModule extends ReactContextBaseJavaModule {
21
+ private static final String MODULE_NAME = "DebugToolkitDevConnect";
22
+ private static final String DEBUG_SERVER_HOST_KEY = "debug_http_host";
23
+ private static final String APPLY_RELOAD_REASON = "DebugToolkit DevConnect Metro host changed";
24
+ private static final String RESET_RELOAD_REASON = "DebugToolkit DevConnect Metro host reset";
25
+
26
+ public DebugToolkitDevConnectModule(ReactApplicationContext reactContext) {
27
+ super(reactContext);
28
+ }
29
+
30
+ @NonNull
31
+ @Override
32
+ public String getName() {
33
+ return MODULE_NAME;
34
+ }
35
+
36
+ private SharedPreferences getPreferences() {
37
+ return PreferenceManager.getDefaultSharedPreferences(getReactApplicationContext());
38
+ }
39
+
40
+ @Nullable
41
+ private Object callGetter(Object target, String methodName) {
42
+ try {
43
+ Method method = target.getClass().getMethod(methodName);
44
+ return method.invoke(target);
45
+ } catch (Exception ignored) {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ private boolean triggerDevSupportReload(@Nullable Object devSupportManager) throws Exception {
51
+ if (devSupportManager == null) {
52
+ return false;
53
+ }
54
+
55
+ Object enabled = callGetter(devSupportManager, "getDevSupportEnabled");
56
+ if (enabled instanceof Boolean && !((Boolean) enabled)) {
57
+ return false;
58
+ }
59
+
60
+ Method reloadMethod = devSupportManager.getClass().getMethod("handleReloadJS");
61
+ reloadMethod.invoke(devSupportManager);
62
+ return true;
63
+ }
64
+
65
+ private boolean reloadFromReactHost(Context applicationContext, String reason) throws Exception {
66
+ Object reactHost = callGetter(applicationContext, "getReactHost");
67
+ if (reactHost == null) {
68
+ return false;
69
+ }
70
+
71
+ if (triggerDevSupportReload(callGetter(reactHost, "getDevSupportManager"))) {
72
+ return true;
73
+ }
74
+
75
+ Method reloadMethod = reactHost.getClass().getMethod("reload", String.class);
76
+ reloadMethod.invoke(reactHost, reason);
77
+ return true;
78
+ }
79
+
80
+ private boolean reloadFromReactNativeHost(Context applicationContext) throws Exception {
81
+ Object reactNativeHost = callGetter(applicationContext, "getReactNativeHost");
82
+ Object instanceManager = reactNativeHost == null
83
+ ? null
84
+ : callGetter(reactNativeHost, "getReactInstanceManager");
85
+ Object devSupportManager = instanceManager == null
86
+ ? null
87
+ : callGetter(instanceManager, "getDevSupportManager");
88
+ return triggerDevSupportReload(devSupportManager);
89
+ }
90
+
91
+ private void resolveAfterReload(String reason, @Nullable WritableMap result, Promise promise) {
92
+ UiThreadUtil.runOnUiThread(() -> {
93
+ try {
94
+ Context applicationContext = getReactApplicationContext().getApplicationContext();
95
+ boolean reloaded = reloadFromReactHost(applicationContext, reason)
96
+ || reloadFromReactNativeHost(applicationContext);
97
+ if (!reloaded) {
98
+ promise.reject("reload_unavailable", "Unable to trigger React Native reload after updating Metro host.");
99
+ return;
100
+ }
101
+ promise.resolve(result);
102
+ } catch (Exception error) {
103
+ promise.reject("reload_failed", "Unable to trigger React Native reload after updating Metro host.", error);
104
+ }
105
+ });
106
+ }
107
+
108
+ @ReactMethod
109
+ public void getMetroHost(Promise promise) {
110
+ @Nullable String host = getPreferences().getString(DEBUG_SERVER_HOST_KEY, null);
111
+ promise.resolve(host);
112
+ }
113
+
114
+ @ReactMethod
115
+ public void applyMetroHost(String hostPort, Promise promise) {
116
+ if (hostPort == null || hostPort.length() == 0) {
117
+ promise.reject("invalid_host", "Metro host cannot be empty.");
118
+ return;
119
+ }
120
+
121
+ boolean stored = getPreferences().edit().putString(DEBUG_SERVER_HOST_KEY, hostPort).commit();
122
+ if (!stored) {
123
+ promise.reject("storage_failed", "Unable to persist Metro host.");
124
+ return;
125
+ }
126
+
127
+ WritableMap result = Arguments.createMap();
128
+ result.putString("hostPort", hostPort);
129
+ resolveAfterReload(APPLY_RELOAD_REASON, result, promise);
130
+ }
131
+
132
+ @ReactMethod
133
+ public void resetMetroHost(Promise promise) {
134
+ boolean stored = getPreferences().edit().remove(DEBUG_SERVER_HOST_KEY).commit();
135
+ if (!stored) {
136
+ promise.reject("storage_failed", "Unable to reset Metro host.");
137
+ return;
138
+ }
139
+
140
+ resolveAfterReload(RESET_RELOAD_REASON, null, promise);
141
+ }
142
+
143
+ @ReactMethod
144
+ public void getPreference(String key, Promise promise) {
145
+ @Nullable String value = getPreferences().getString(key, null);
146
+ promise.resolve(value);
147
+ }
148
+
149
+ @ReactMethod
150
+ public void setPreference(String key, String value, Promise promise) {
151
+ getPreferences().edit().putString(key, value).apply();
152
+ promise.resolve(null);
153
+ }
154
+ }
@@ -0,0 +1,25 @@
1
+ package com.reactnativedebugtoolkit;
2
+
3
+ import androidx.annotation.NonNull;
4
+
5
+ import com.facebook.react.ReactPackage;
6
+ import com.facebook.react.bridge.NativeModule;
7
+ import com.facebook.react.bridge.ReactApplicationContext;
8
+ import com.facebook.react.uimanager.ViewManager;
9
+
10
+ import java.util.Collections;
11
+ import java.util.List;
12
+
13
+ public class ReactNativeDebugToolkitPackage implements ReactPackage {
14
+ @NonNull
15
+ @Override
16
+ public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
17
+ return Collections.<NativeModule>singletonList(new DebugToolkitDevConnectModule(reactContext));
18
+ }
19
+
20
+ @NonNull
21
+ @Override
22
+ public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
23
+ return Collections.emptyList();
24
+ }
25
+ }
@@ -0,0 +1,81 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <React/RCTBridgeModule.h>
3
+ #import <React/RCTBundleURLProvider.h>
4
+ #import <React/RCTReloadCommand.h>
5
+
6
+ static NSString *const DebugToolkitRCTJsLocationKey = @"RCT_jsLocation";
7
+ static NSString *const DebugToolkitBundleRoot = @"index";
8
+
9
+ static void DebugToolkitResolveAfterReload(NSString *reason, id result, RCTPromiseResolveBlock resolve)
10
+ {
11
+ dispatch_async(dispatch_get_main_queue(), ^{
12
+ NSURL *bundleURL = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:DebugToolkitBundleRoot];
13
+ if (bundleURL) {
14
+ RCTReloadCommandSetBundleURL(bundleURL);
15
+ }
16
+ RCTTriggerReloadCommandListeners(reason);
17
+ resolve(result ?: [NSNull null]);
18
+ });
19
+ }
20
+
21
+ @interface DebugToolkitDevConnect : NSObject <RCTBridgeModule>
22
+ @end
23
+
24
+ @implementation DebugToolkitDevConnect
25
+
26
+ RCT_EXPORT_MODULE(DebugToolkitDevConnect)
27
+
28
+ + (BOOL)requiresMainQueueSetup
29
+ {
30
+ return NO;
31
+ }
32
+
33
+ RCT_EXPORT_METHOD(getMetroHost:(RCTPromiseResolveBlock)resolve
34
+ rejecter:(__unused RCTPromiseRejectBlock)reject)
35
+ {
36
+ NSString *host = [RCTBundleURLProvider sharedSettings].jsLocation;
37
+ resolve(host ?: [NSNull null]);
38
+ }
39
+
40
+ RCT_EXPORT_METHOD(applyMetroHost:(NSString *)hostPort
41
+ resolver:(RCTPromiseResolveBlock)resolve
42
+ rejecter:(RCTPromiseRejectBlock)reject)
43
+ {
44
+ if (hostPort.length == 0) {
45
+ reject(@"invalid_host", @"Metro host cannot be empty.", nil);
46
+ return;
47
+ }
48
+
49
+ [RCTBundleURLProvider sharedSettings].jsLocation = hostPort;
50
+ DebugToolkitResolveAfterReload(@"DebugToolkit DevConnect Metro host changed", @{ @"hostPort" : hostPort }, resolve);
51
+ }
52
+
53
+ RCT_EXPORT_METHOD(resetMetroHost:(RCTPromiseResolveBlock)resolve
54
+ rejecter:(__unused RCTPromiseRejectBlock)reject)
55
+ {
56
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
57
+ [defaults removeObjectForKey:DebugToolkitRCTJsLocationKey];
58
+ [defaults synchronize];
59
+ [[NSNotificationCenter defaultCenter] postNotificationName:RCTBundleURLProviderUpdatedNotification object:nil];
60
+ DebugToolkitResolveAfterReload(@"DebugToolkit DevConnect Metro host reset", [NSNull null], resolve);
61
+ }
62
+
63
+ RCT_EXPORT_METHOD(getPreference:(NSString *)key
64
+ resolver:(RCTPromiseResolveBlock)resolve
65
+ rejecter:(__unused RCTPromiseRejectBlock)reject)
66
+ {
67
+ NSString *value = [[NSUserDefaults standardUserDefaults] stringForKey:key];
68
+ resolve(value ?: [NSNull null]);
69
+ }
70
+
71
+ RCT_EXPORT_METHOD(setPreference:(NSString *)key
72
+ value:(NSString *)value
73
+ resolver:(RCTPromiseResolveBlock)resolve
74
+ rejecter:(__unused RCTPromiseRejectBlock)reject)
75
+ {
76
+ [[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
77
+ [[NSUserDefaults standardUserDefaults] synchronize];
78
+ resolve([NSNull null]);
79
+ }
80
+
81
+ @end
@@ -27,7 +27,9 @@ class CameraErrorBoundary extends _react.Component {
27
27
  this.props.onCameraError(error.message || 'Camera failed to initialize.');
28
28
  }
29
29
  render() {
30
- if (this.state.hasError) return null;
30
+ if (this.state.hasError) {
31
+ return null;
32
+ }
31
33
  return this.props.children;
32
34
  }
33
35
  }
@@ -37,7 +39,7 @@ class CameraErrorBoundary extends _react.Component {
37
39
  function DevConnectQrScanner({
38
40
  visible,
39
41
  onClose,
40
- onScanHost
42
+ onScanTarget
41
43
  }) {
42
44
  const scannedRef = (0, _react.useRef)(false);
43
45
  const [error, setError] = (0, _react.useState)(null);
@@ -51,8 +53,12 @@ function DevConnectQrScanner({
51
53
  }
52
54
  }, [visible]);
53
55
  const handleScanned = (0, _react.useCallback)(rawValue => {
54
- if (scannedRef.current) return;
55
- if (typeof rawValue !== 'string') return;
56
+ if (scannedRef.current) {
57
+ return;
58
+ }
59
+ if (typeof rawValue !== 'string') {
60
+ return;
61
+ }
56
62
  const parsed = (0, _devConnectUtils.parseMetroQrPayload)(rawValue);
57
63
  if (!parsed) {
58
64
  setError('QR code does not contain a supported Metro URL.');
@@ -60,9 +66,12 @@ function DevConnectQrScanner({
60
66
  }
61
67
  scannedRef.current = true;
62
68
  setError(null);
63
- onScanHost(parsed.computerHost);
69
+ onScanTarget({
70
+ computerHost: parsed.computerHost,
71
+ metroPort: parsed.metroPort
72
+ });
64
73
  onClose();
65
- }, [onClose, onScanHost]);
74
+ }, [onClose, onScanTarget]);
66
75
  const handleCameraKitRead = (0, _react.useCallback)(event => {
67
76
  handleScanned(event.nativeEvent?.codeStringValue ?? '');
68
77
  }, [handleScanned]);
@@ -72,63 +81,71 @@ function DevConnectQrScanner({
72
81
  const handleCameraError = (0, _react.useCallback)(_msg => {
73
82
  setCameraFailed(true);
74
83
  }, []);
75
- if (!visible || !scanner) return null;
84
+ if (!visible || !scanner) {
85
+ return null;
86
+ }
76
87
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
77
88
  visible: visible,
78
89
  animationType: "slide",
90
+ presentationStyle: "fullScreen",
79
91
  onRequestClose: onClose,
80
92
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
81
93
  style: styles.container,
82
- children: [!cameraFailed && /*#__PURE__*/(0, _jsxRuntime.jsx)(CameraErrorBoundary, {
83
- onCameraError: handleCameraError,
84
- children: scanner.kind === 'camera-kit' && scanner.CameraKit ? /*#__PURE__*/(0, _jsxRuntime.jsx)(scanner.CameraKit.Camera, {
85
- style: styles.camera,
86
- cameraType: scanner.CameraKit.CameraType?.Back,
87
- scanBarcode: true,
88
- onReadCode: handleCameraKitRead,
89
- showFrame: true,
90
- laserColor: _colors.Colors.primary,
91
- frameColor: _colors.Colors.primary,
92
- allowedBarcodeTypes: ['qr']
93
- }) : scanner.kind === 'expo-camera' && scanner.ExpoCamera ? /*#__PURE__*/(0, _jsxRuntime.jsx)(scanner.ExpoCamera.Camera, {
94
- style: styles.camera,
95
- onBarCodeScanned: handleExpoScanned,
96
- barCodeScannerSettings: {
97
- barCodeTypes: ['qr']
98
- }
99
- }) : null
100
- }), cameraFailed && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
101
- style: styles.cameraFallback,
102
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
103
- style: styles.cameraFallbackText,
104
- children: "Camera unavailable."
105
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
106
- style: styles.cameraFallbackHint,
107
- children: "Please enter computer IP manually."
94
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
95
+ style: styles.previewLayer,
96
+ children: [!cameraFailed && /*#__PURE__*/(0, _jsxRuntime.jsx)(CameraErrorBoundary, {
97
+ onCameraError: handleCameraError,
98
+ children: scanner.kind === 'camera-kit' && scanner.CameraKit ? /*#__PURE__*/(0, _jsxRuntime.jsx)(scanner.CameraKit.Camera, {
99
+ style: styles.camera,
100
+ cameraType: scanner.CameraKit.CameraType?.Back,
101
+ scanBarcode: true,
102
+ onReadCode: handleCameraKitRead,
103
+ showFrame: true,
104
+ laserColor: _colors.Colors.primary,
105
+ frameColor: _colors.Colors.primary,
106
+ allowedBarcodeTypes: ['qr']
107
+ }) : scanner.kind === 'expo-camera' && scanner.ExpoCamera ? /*#__PURE__*/(0, _jsxRuntime.jsx)(scanner.ExpoCamera.Camera, {
108
+ style: styles.camera,
109
+ onBarCodeScanned: handleExpoScanned,
110
+ barCodeScannerSettings: {
111
+ barCodeTypes: ['qr']
112
+ }
113
+ }) : null
114
+ }), cameraFailed && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
115
+ style: styles.cameraFallback,
116
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
117
+ style: styles.cameraFallbackText,
118
+ children: "Camera unavailable."
119
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
120
+ style: styles.cameraFallbackHint,
121
+ children: "Please enter computer IP manually."
122
+ })]
108
123
  })]
109
124
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
110
- style: styles.footer,
111
- children: [!cameraFailed && !error && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
112
- style: styles.hint,
113
- children: "Scan a Metro QR code."
114
- }), error && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
115
- style: styles.error,
116
- children: error
125
+ style: styles.topBar,
126
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
127
+ style: styles.titleGroup,
128
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
129
+ style: styles.title,
130
+ children: "Scan Metro QR"
131
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
132
+ style: styles.subtitle,
133
+ children: "Expo or Metro URL"
134
+ })]
117
135
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
118
136
  style: styles.closeButton,
119
137
  onPress: onClose,
120
- activeOpacity: 0.7,
138
+ activeOpacity: 0.75,
121
139
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
122
140
  style: styles.closeButtonText,
123
141
  children: "Close"
124
142
  })
125
143
  })]
126
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
127
- style: styles.topClose,
128
- onPress: onClose,
144
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
145
+ style: [styles.statusPill, error && styles.statusPillError],
129
146
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
130
- style: styles.topCloseText,
131
- children: "Close"
147
+ style: [styles.statusText, error && styles.statusTextError],
148
+ children: error ?? 'Point the camera at exp:// or http:// Metro URL.'
132
149
  })
133
150
  })]
134
151
  })
@@ -139,11 +156,14 @@ const styles = _reactNative.StyleSheet.create({
139
156
  flex: 1,
140
157
  backgroundColor: '#000'
141
158
  },
159
+ previewLayer: {
160
+ ..._reactNative.StyleSheet.absoluteFillObject
161
+ },
142
162
  camera: {
143
- flex: 1
163
+ ..._reactNative.StyleSheet.absoluteFillObject
144
164
  },
145
165
  cameraFallback: {
146
- flex: 1,
166
+ ..._reactNative.StyleSheet.absoluteFillObject,
147
167
  justifyContent: 'center',
148
168
  alignItems: 'center',
149
169
  padding: 24
@@ -159,45 +179,70 @@ const styles = _reactNative.StyleSheet.create({
159
179
  color: 'rgba(255,255,255,0.6)',
160
180
  textAlign: 'center'
161
181
  },
162
- footer: {
163
- padding: 16,
164
- backgroundColor: _colors.Colors.surface
182
+ topBar: {
183
+ position: 'absolute',
184
+ top: 44,
185
+ left: 16,
186
+ right: 16,
187
+ flexDirection: 'row',
188
+ alignItems: 'center',
189
+ justifyContent: 'space-between',
190
+ paddingLeft: 14,
191
+ paddingRight: 8,
192
+ paddingVertical: 8,
193
+ borderRadius: 12,
194
+ backgroundColor: 'rgba(0,0,0,0.62)'
165
195
  },
166
- hint: {
167
- fontSize: 13,
168
- color: _colors.Colors.textSecondary,
169
- marginBottom: 12
196
+ titleGroup: {
197
+ flex: 1,
198
+ paddingRight: 10
170
199
  },
171
- error: {
172
- fontSize: 13,
173
- color: _colors.Colors.error,
174
- marginBottom: 12
200
+ title: {
201
+ color: '#fff',
202
+ fontSize: 15,
203
+ fontWeight: '700'
204
+ },
205
+ subtitle: {
206
+ color: 'rgba(255,255,255,0.68)',
207
+ fontSize: 11,
208
+ marginTop: 2
175
209
  },
176
210
  closeButton: {
177
211
  alignItems: 'center',
178
212
  justifyContent: 'center',
179
- paddingVertical: 11,
180
- borderRadius: 10,
181
- backgroundColor: _colors.Colors.primary
213
+ paddingHorizontal: 12,
214
+ paddingVertical: 8,
215
+ borderRadius: 8,
216
+ backgroundColor: 'rgba(255,255,255,0.14)'
182
217
  },
183
218
  closeButtonText: {
184
219
  color: '#fff',
185
220
  fontSize: 14,
186
221
  fontWeight: '600'
187
222
  },
188
- topClose: {
223
+ statusPill: {
189
224
  position: 'absolute',
190
- top: 48,
225
+ left: 16,
191
226
  right: 16,
192
- paddingHorizontal: 12,
193
- paddingVertical: 8,
194
- borderRadius: 8,
195
- backgroundColor: 'rgba(0,0,0,0.55)'
227
+ bottom: 30,
228
+ paddingHorizontal: 14,
229
+ paddingVertical: 11,
230
+ borderRadius: 12,
231
+ backgroundColor: 'rgba(0,0,0,0.62)'
196
232
  },
197
- topCloseText: {
233
+ statusPillError: {
234
+ backgroundColor: `${_colors.Colors.error}22`,
235
+ borderWidth: 1,
236
+ borderColor: `${_colors.Colors.error}66`
237
+ },
238
+ statusText: {
198
239
  color: '#fff',
199
240
  fontSize: 13,
200
- fontWeight: '600'
241
+ textAlign: 'center',
242
+ lineHeight: 18
243
+ },
244
+ statusTextError: {
245
+ color: '#fff'
201
246
  }
202
247
  });
203
248
  //# sourceMappingURL=DevConnectQrScanner.js.map