react-native-image-stitcher 0.1.0

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 (151) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +21 -0
  4. package/README.md +189 -0
  5. package/RNImageStitcher.podspec +76 -0
  6. package/android/build.gradle +224 -0
  7. package/android/src/main/AndroidManifest.xml +3 -0
  8. package/android/src/main/cpp/CMakeLists.txt +124 -0
  9. package/android/src/main/cpp/image_stitcher_jni.cpp +145 -0
  10. package/android/src/main/cpp/keyframe_gate_jni.cpp +204 -0
  11. package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +426 -0
  12. package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +960 -0
  13. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +2371 -0
  14. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +256 -0
  15. package/android/src/main/java/io/imagestitcher/rn/QualityChecker.kt +167 -0
  16. package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +39 -0
  17. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +558 -0
  18. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraViewManager.kt +35 -0
  19. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +784 -0
  20. package/android/src/main/java/io/imagestitcher/rn/ar/BackgroundRenderer.kt +176 -0
  21. package/android/src/main/java/io/imagestitcher/rn/ar/ShaderUtil.kt +67 -0
  22. package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +201 -0
  23. package/cpp/ar_frame_pose.h +63 -0
  24. package/cpp/keyframe_gate.cpp +927 -0
  25. package/cpp/keyframe_gate.hpp +240 -0
  26. package/cpp/stitcher.cpp +2207 -0
  27. package/cpp/stitcher.hpp +275 -0
  28. package/dist/ar/useARSession.d.ts +102 -0
  29. package/dist/ar/useARSession.js +133 -0
  30. package/dist/camera/ARCameraView.d.ts +93 -0
  31. package/dist/camera/ARCameraView.js +170 -0
  32. package/dist/camera/Camera.d.ts +134 -0
  33. package/dist/camera/Camera.js +688 -0
  34. package/dist/camera/CameraShutter.d.ts +80 -0
  35. package/dist/camera/CameraShutter.js +237 -0
  36. package/dist/camera/CameraView.d.ts +65 -0
  37. package/dist/camera/CameraView.js +117 -0
  38. package/dist/camera/CaptureControlsBar.d.ts +87 -0
  39. package/dist/camera/CaptureControlsBar.js +82 -0
  40. package/dist/camera/CaptureHeader.d.ts +62 -0
  41. package/dist/camera/CaptureHeader.js +81 -0
  42. package/dist/camera/CapturePreview.d.ts +70 -0
  43. package/dist/camera/CapturePreview.js +188 -0
  44. package/dist/camera/CaptureStatusOverlay.d.ts +75 -0
  45. package/dist/camera/CaptureStatusOverlay.js +326 -0
  46. package/dist/camera/CaptureThumbnailStrip.d.ts +87 -0
  47. package/dist/camera/CaptureThumbnailStrip.js +177 -0
  48. package/dist/camera/IncrementalPanGuide.d.ts +83 -0
  49. package/dist/camera/IncrementalPanGuide.js +267 -0
  50. package/dist/camera/PanoramaBandOverlay.d.ts +107 -0
  51. package/dist/camera/PanoramaBandOverlay.js +399 -0
  52. package/dist/camera/PanoramaConfirmModal.d.ts +57 -0
  53. package/dist/camera/PanoramaConfirmModal.js +128 -0
  54. package/dist/camera/PanoramaGuidance.d.ts +79 -0
  55. package/dist/camera/PanoramaGuidance.js +246 -0
  56. package/dist/camera/PanoramaSettingsModal.d.ts +311 -0
  57. package/dist/camera/PanoramaSettingsModal.js +611 -0
  58. package/dist/camera/ViewportCropOverlay.d.ts +46 -0
  59. package/dist/camera/ViewportCropOverlay.js +67 -0
  60. package/dist/camera/useCapture.d.ts +111 -0
  61. package/dist/camera/useCapture.js +160 -0
  62. package/dist/camera/useDeviceOrientation.d.ts +48 -0
  63. package/dist/camera/useDeviceOrientation.js +131 -0
  64. package/dist/camera/useVideoCapture.d.ts +79 -0
  65. package/dist/camera/useVideoCapture.js +151 -0
  66. package/dist/index.d.ts +26 -0
  67. package/dist/index.js +39 -0
  68. package/dist/quality/normaliseOrientation.d.ts +36 -0
  69. package/dist/quality/normaliseOrientation.js +62 -0
  70. package/dist/quality/runQualityCheck.d.ts +41 -0
  71. package/dist/quality/runQualityCheck.js +98 -0
  72. package/dist/sensors/useIMUTranslationGate.d.ts +70 -0
  73. package/dist/sensors/useIMUTranslationGate.js +235 -0
  74. package/dist/stitching/IncrementalStitcherView.d.ts +41 -0
  75. package/dist/stitching/IncrementalStitcherView.js +157 -0
  76. package/dist/stitching/incremental.d.ts +930 -0
  77. package/dist/stitching/incremental.js +133 -0
  78. package/dist/stitching/stitchFrames.d.ts +55 -0
  79. package/dist/stitching/stitchFrames.js +56 -0
  80. package/dist/stitching/stitchVideo.d.ts +119 -0
  81. package/dist/stitching/stitchVideo.js +57 -0
  82. package/dist/stitching/useIncrementalJSDriver.d.ts +74 -0
  83. package/dist/stitching/useIncrementalJSDriver.js +199 -0
  84. package/dist/stitching/useIncrementalStitcher.d.ts +58 -0
  85. package/dist/stitching/useIncrementalStitcher.js +172 -0
  86. package/dist/types.d.ts +58 -0
  87. package/dist/types.js +15 -0
  88. package/ios/Package.swift +72 -0
  89. package/ios/Sources/RNImageStitcher/ARCameraViewManager.m +33 -0
  90. package/ios/Sources/RNImageStitcher/ARCameraViewManager.swift +40 -0
  91. package/ios/Sources/RNImageStitcher/ARSessionBridge.m +55 -0
  92. package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +149 -0
  93. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +2727 -0
  94. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +85 -0
  95. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +625 -0
  96. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +328 -0
  97. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +141 -0
  98. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +278 -0
  99. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +473 -0
  100. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +1326 -0
  101. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +97 -0
  102. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +296 -0
  103. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +103 -0
  104. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +3285 -0
  105. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +238 -0
  106. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +1880 -0
  107. package/ios/Sources/RNImageStitcher/QualityChecker.swift +252 -0
  108. package/ios/Sources/RNImageStitcher/QualityCheckerBridge.m +26 -0
  109. package/ios/Sources/RNImageStitcher/QualityCheckerBridge.swift +72 -0
  110. package/ios/Sources/RNImageStitcher/RNSARCameraView.swift +114 -0
  111. package/ios/Sources/RNImageStitcher/RNSARSession.swift +1111 -0
  112. package/ios/Sources/RNImageStitcher/Stitcher.swift +243 -0
  113. package/ios/Sources/RNImageStitcher/StitcherBridge.m +28 -0
  114. package/ios/Sources/RNImageStitcher/StitcherBridge.swift +246 -0
  115. package/package.json +73 -0
  116. package/react-native.config.js +34 -0
  117. package/scripts/opencv-version.txt +1 -0
  118. package/scripts/postinstall-fetch-binaries.js +286 -0
  119. package/src/ar/useARSession.ts +210 -0
  120. package/src/camera/.gitkeep +0 -0
  121. package/src/camera/ARCameraView.tsx +256 -0
  122. package/src/camera/Camera.tsx +1053 -0
  123. package/src/camera/CameraShutter.tsx +292 -0
  124. package/src/camera/CameraView.tsx +157 -0
  125. package/src/camera/CaptureControlsBar.tsx +204 -0
  126. package/src/camera/CaptureHeader.tsx +184 -0
  127. package/src/camera/CapturePreview.tsx +318 -0
  128. package/src/camera/CaptureStatusOverlay.tsx +391 -0
  129. package/src/camera/CaptureThumbnailStrip.tsx +277 -0
  130. package/src/camera/IncrementalPanGuide.tsx +328 -0
  131. package/src/camera/PanoramaBandOverlay.tsx +498 -0
  132. package/src/camera/PanoramaConfirmModal.tsx +206 -0
  133. package/src/camera/PanoramaGuidance.tsx +327 -0
  134. package/src/camera/PanoramaSettingsModal.tsx +1357 -0
  135. package/src/camera/ViewportCropOverlay.tsx +81 -0
  136. package/src/camera/useCapture.ts +279 -0
  137. package/src/camera/useDeviceOrientation.ts +140 -0
  138. package/src/camera/useVideoCapture.ts +236 -0
  139. package/src/index.ts +53 -0
  140. package/src/quality/.gitkeep +0 -0
  141. package/src/quality/normaliseOrientation.ts +79 -0
  142. package/src/quality/runQualityCheck.ts +131 -0
  143. package/src/sensors/useIMUTranslationGate.ts +347 -0
  144. package/src/stitching/.gitkeep +0 -0
  145. package/src/stitching/IncrementalStitcherView.tsx +198 -0
  146. package/src/stitching/incremental.ts +1021 -0
  147. package/src/stitching/stitchFrames.ts +88 -0
  148. package/src/stitching/stitchVideo.ts +153 -0
  149. package/src/stitching/useIncrementalJSDriver.ts +273 -0
  150. package/src/stitching/useIncrementalStitcher.ts +252 -0
  151. package/src/types.ts +78 -0
@@ -0,0 +1,62 @@
1
+ /**
2
+ * CaptureHeader — top-of-screen header for any capture surface.
3
+ *
4
+ * ┌──────────────────────────────────────────────────────────┐
5
+ * │ ‹ Back Cola Promo End Cap │
6
+ * │ Photograph the promotional cola end cap. │
7
+ * └──────────────────────────────────────────────────────────┘
8
+ *
9
+ * Two stacked rows — title row (back arrow + centred title) and an
10
+ * optional guidance line below. Lives in the SDK so every capture
11
+ * surface gets identical chrome without re-implementing safe-area
12
+ * handling, accessibility labels, and contrast on a black preview.
13
+ *
14
+ * Theming:
15
+ * The host has its own theme system; rather than coupling the SDK
16
+ * to it, the component exposes a small set of color props
17
+ * (defaulted to white-on-black for visibility against the camera
18
+ * preview). Hosts that want to override pass `colors`.
19
+ */
20
+ import React from 'react';
21
+ import { type StyleProp, type ViewStyle } from 'react-native';
22
+ export interface CaptureHeaderProps {
23
+ /** The audit / surface title. Centred horizontally. */
24
+ title: string;
25
+ /**
26
+ * Called when the back affordance is pressed. If omitted, the
27
+ * back button is hidden entirely (use this for surfaces invoked
28
+ * as a top-level tab where back doesn't make sense).
29
+ */
30
+ onBack?: () => void;
31
+ /** Custom label for the back button. Defaults to "‹ Back". */
32
+ backLabel?: string;
33
+ /**
34
+ * Called when the gear / settings affordance is pressed. If
35
+ * omitted, no settings icon is rendered. Wire this to the
36
+ * host's PanoramaSettingsModal `visible` state.
37
+ */
38
+ onSettingsPress?: () => void;
39
+ /**
40
+ * Optional second-line guidance text shown below the title row.
41
+ * Renders nothing if absent.
42
+ */
43
+ guidance?: string;
44
+ /**
45
+ * Top inset in pixels. Pass `useSafeAreaInsets().top` from
46
+ * react-native-safe-area-context if your app uses it; otherwise a
47
+ * sensible default is applied.
48
+ */
49
+ topInset?: number;
50
+ /** Override the default text/background colors. */
51
+ colors?: {
52
+ background?: string;
53
+ title?: string;
54
+ accent?: string;
55
+ guidanceBackground?: string;
56
+ guidanceText?: string;
57
+ };
58
+ /** Additional style applied to the outer container. */
59
+ style?: StyleProp<ViewStyle>;
60
+ }
61
+ export declare function CaptureHeader({ title, onBack, backLabel, onSettingsPress, guidance, topInset, colors, style, }: CaptureHeaderProps): React.JSX.Element;
62
+ //# sourceMappingURL=CaptureHeader.d.ts.map
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * CaptureHeader — top-of-screen header for any capture surface.
5
+ *
6
+ * ┌──────────────────────────────────────────────────────────┐
7
+ * │ ‹ Back Cola Promo End Cap │
8
+ * │ Photograph the promotional cola end cap. │
9
+ * └──────────────────────────────────────────────────────────┘
10
+ *
11
+ * Two stacked rows — title row (back arrow + centred title) and an
12
+ * optional guidance line below. Lives in the SDK so every capture
13
+ * surface gets identical chrome without re-implementing safe-area
14
+ * handling, accessibility labels, and contrast on a black preview.
15
+ *
16
+ * Theming:
17
+ * The host has its own theme system; rather than coupling the SDK
18
+ * to it, the component exposes a small set of color props
19
+ * (defaulted to white-on-black for visibility against the camera
20
+ * preview). Hosts that want to override pass `colors`.
21
+ */
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.CaptureHeader = CaptureHeader;
27
+ const react_1 = __importDefault(require("react"));
28
+ const react_native_1 = require("react-native");
29
+ function CaptureHeader({ title, onBack, backLabel = '‹ Back', onSettingsPress, guidance, topInset = 0, colors, style, }) {
30
+ const bg = colors?.background ?? '#000000';
31
+ const titleColor = colors?.title ?? '#ffffff';
32
+ const accent = colors?.accent ?? '#FF9F0A';
33
+ const guidanceBg = colors?.guidanceBackground ?? 'rgba(255,255,255,0.08)';
34
+ const guidanceColor = colors?.guidanceText ?? '#ffffff';
35
+ return (react_1.default.createElement(react_native_1.View, { style: [{ backgroundColor: bg }, style] },
36
+ react_1.default.createElement(react_native_1.View, { style: [styles.titleRow, { paddingTop: topInset + 8 }] },
37
+ onBack ? (react_1.default.createElement(react_native_1.Pressable, { onPress: onBack, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: "Go back", style: styles.backButton },
38
+ react_1.default.createElement(react_native_1.Text, { style: [styles.backText, { color: accent }] }, backLabel))) : (
39
+ // Empty spacer keeps the title centred even when back is hidden.
40
+ react_1.default.createElement(react_native_1.View, { style: styles.backButton })),
41
+ react_1.default.createElement(react_native_1.Text, { style: [styles.title, { color: titleColor }], numberOfLines: 1, accessibilityRole: "header" }, title),
42
+ onSettingsPress ? (react_1.default.createElement(react_native_1.Pressable, { onPress: onSettingsPress, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: "Open panorama settings", style: styles.backButton },
43
+ react_1.default.createElement(react_native_1.Text, { style: [styles.gearIcon, { color: accent }] }, "\u2699"))) : (react_1.default.createElement(react_native_1.View, { style: styles.backButton }))),
44
+ guidance ? (react_1.default.createElement(react_native_1.View, { style: [styles.guidance, { backgroundColor: guidanceBg }], accessibilityRole: "text" },
45
+ react_1.default.createElement(react_native_1.Text, { style: [styles.guidanceText, { color: guidanceColor }], numberOfLines: 2 }, guidance))) : null));
46
+ }
47
+ const styles = react_native_1.StyleSheet.create({
48
+ titleRow: {
49
+ flexDirection: 'row',
50
+ alignItems: 'center',
51
+ justifyContent: 'space-between',
52
+ paddingHorizontal: 16,
53
+ paddingBottom: 8,
54
+ },
55
+ backButton: {
56
+ minWidth: 64,
57
+ paddingVertical: 4,
58
+ },
59
+ backText: {
60
+ fontSize: 16,
61
+ fontWeight: '500',
62
+ },
63
+ title: {
64
+ flex: 1,
65
+ textAlign: 'center',
66
+ fontSize: 16,
67
+ fontWeight: '600',
68
+ },
69
+ guidance: {
70
+ paddingHorizontal: 16,
71
+ paddingVertical: 8,
72
+ },
73
+ guidanceText: {
74
+ fontSize: 13,
75
+ },
76
+ gearIcon: {
77
+ fontSize: 22,
78
+ textAlign: 'right',
79
+ },
80
+ });
81
+ //# sourceMappingURL=CaptureHeader.js.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * CapturePreview — shared full-screen image preview used for BOTH:
3
+ * 1. Tap-to-preview from <CaptureThumbnailStrip> (existing thumbnails)
4
+ * 2. Post-stitch confirmation (newly produced panorama)
5
+ *
6
+ * ┌──────────────────────────────────────────────────────────┐
7
+ * │ [✕ close] │
8
+ * │ ┌──────────────────────────────┐ │
9
+ * │ │ image (resizeMode= │ │
10
+ * │ │ contain) │ │
11
+ * │ └──────────────────────────────┘ │
12
+ * │ │
13
+ * │ [action 1] [action 2] [action 3] │
14
+ * └──────────────────────────────────────────────────────────┘
15
+ *
16
+ * Actions are passed in by the host so a single component can
17
+ * render the right buttons for each context:
18
+ * - Thumbnail tap, unsubmitted capture → [Delete] [Recapture]
19
+ * - Thumbnail tap, already-synced capture → [] (just close)
20
+ * - Post-stitch → [Discard] [Retry] [Save]
21
+ *
22
+ * The component is presentational — it does not know about audit
23
+ * state, sync state, or any host-domain concept beyond the URI +
24
+ * dimensions to display and the action callbacks to fire.
25
+ */
26
+ import React from 'react';
27
+ export type CapturePreviewActionVariant = 'primary' | 'neutral' | 'ghost' | 'destructive';
28
+ export interface CapturePreviewAction {
29
+ /** Button label (visible text). */
30
+ label: string;
31
+ /** Optional leading glyph — usually a unicode arrow / check / cross. */
32
+ icon?: string;
33
+ /** Visual variant. Defaults to "neutral". */
34
+ variant?: CapturePreviewActionVariant;
35
+ /** Disabled state — useful while an async action is in flight. */
36
+ disabled?: boolean;
37
+ /**
38
+ * Called when the user presses the button. Caller decides
39
+ * whether the preview should close after — call `onClose` from
40
+ * inside if that's the desired behaviour.
41
+ */
42
+ onPress: () => void;
43
+ }
44
+ export interface CapturePreviewProps {
45
+ /** Whether the modal is shown. Drive from host state. */
46
+ visible: boolean;
47
+ /** file:// or http(s) URI to display. */
48
+ imageUri: string;
49
+ /** Image width in px (for aspect-ratio rendering). */
50
+ imageWidth?: number;
51
+ /** Image height in px. */
52
+ imageHeight?: number;
53
+ /**
54
+ * Action buttons rendered along the bottom. Empty array (or
55
+ * undefined) renders no buttons — only the close affordance is
56
+ * available. Up to 3 actions display cleanly across most
57
+ * widths; more wraps on a typical phone.
58
+ */
59
+ actions?: CapturePreviewAction[];
60
+ /**
61
+ * Called when the user dismisses the preview without choosing an
62
+ * action — tap on the close button, tap on the backdrop outside
63
+ * the image, or hardware back on Android.
64
+ */
65
+ onClose: () => void;
66
+ /** Optional title shown at the top of the modal. */
67
+ title?: string;
68
+ }
69
+ export declare function CapturePreview({ visible, imageUri, imageWidth, imageHeight, actions, onClose, title, }: CapturePreviewProps): React.JSX.Element;
70
+ //# sourceMappingURL=CapturePreview.d.ts.map
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * CapturePreview — shared full-screen image preview used for BOTH:
5
+ * 1. Tap-to-preview from <CaptureThumbnailStrip> (existing thumbnails)
6
+ * 2. Post-stitch confirmation (newly produced panorama)
7
+ *
8
+ * ┌──────────────────────────────────────────────────────────┐
9
+ * │ [✕ close] │
10
+ * │ ┌──────────────────────────────┐ │
11
+ * │ │ image (resizeMode= │ │
12
+ * │ │ contain) │ │
13
+ * │ └──────────────────────────────┘ │
14
+ * │ │
15
+ * │ [action 1] [action 2] [action 3] │
16
+ * └──────────────────────────────────────────────────────────┘
17
+ *
18
+ * Actions are passed in by the host so a single component can
19
+ * render the right buttons for each context:
20
+ * - Thumbnail tap, unsubmitted capture → [Delete] [Recapture]
21
+ * - Thumbnail tap, already-synced capture → [] (just close)
22
+ * - Post-stitch → [Discard] [Retry] [Save]
23
+ *
24
+ * The component is presentational — it does not know about audit
25
+ * state, sync state, or any host-domain concept beyond the URI +
26
+ * dimensions to display and the action callbacks to fire.
27
+ */
28
+ var __importDefault = (this && this.__importDefault) || function (mod) {
29
+ return (mod && mod.__esModule) ? mod : { "default": mod };
30
+ };
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.CapturePreview = CapturePreview;
33
+ const react_1 = __importDefault(require("react"));
34
+ const react_native_1 = require("react-native");
35
+ function CapturePreview({ visible, imageUri, imageWidth, imageHeight, actions, onClose, title, }) {
36
+ const aspectRatio = imageWidth && imageHeight && imageWidth > 0 && imageHeight > 0
37
+ ? imageWidth / imageHeight
38
+ : 16 / 9;
39
+ const hasActions = actions && actions.length > 0;
40
+ return (react_1.default.createElement(react_native_1.Modal, { visible: visible, animationType: "fade", transparent: true, statusBarTranslucent: true, onRequestClose: onClose },
41
+ react_1.default.createElement(react_native_1.View, { style: styles.backdrop },
42
+ react_1.default.createElement(react_native_1.View, { style: styles.topBar },
43
+ react_1.default.createElement(react_native_1.View, { style: styles.topBarSpacer }),
44
+ title ? (react_1.default.createElement(react_native_1.Text, { style: styles.title, accessibilityRole: "header" }, title)) : (react_1.default.createElement(react_native_1.View, { style: styles.topBarSpacer })),
45
+ react_1.default.createElement(react_native_1.Pressable, { onPress: onClose, hitSlop: 20, style: styles.closeButton, accessibilityRole: "button", accessibilityLabel: "Close preview" },
46
+ react_1.default.createElement(react_native_1.Text, { style: styles.closeText }, "\u00D7"))),
47
+ react_1.default.createElement(react_native_1.Pressable, { style: styles.imageWrapper, onPress: onClose, accessibilityRole: "button", accessibilityLabel: "Close preview" },
48
+ react_1.default.createElement(react_native_1.Image, { source: { uri: imageUri }, style: [styles.image, { aspectRatio }], resizeMode: "contain", accessibilityIgnoresInvertColors: true })),
49
+ hasActions ? (react_1.default.createElement(react_native_1.View, { style: styles.buttonRow }, actions.map((action, idx) => (react_1.default.createElement(react_native_1.Pressable, { key: `${action.label}-${idx}`, onPress: action.onPress, disabled: action.disabled, style: [
50
+ styles.button,
51
+ buttonStyleFor(action.variant ?? 'neutral'),
52
+ action.disabled ? styles.buttonDisabled : null,
53
+ ], accessibilityRole: "button", accessibilityLabel: action.label, accessibilityState: { disabled: action.disabled } },
54
+ react_1.default.createElement(react_native_1.Text, { style: [
55
+ styles.buttonText,
56
+ buttonTextStyleFor(action.variant ?? 'neutral'),
57
+ ], numberOfLines: 1 },
58
+ action.icon ? `${action.icon} ` : '',
59
+ action.label)))))) : null)));
60
+ }
61
+ function buttonStyleFor(variant) {
62
+ switch (variant) {
63
+ case 'primary':
64
+ return styles.buttonPrimary;
65
+ case 'destructive':
66
+ return styles.buttonDestructive;
67
+ case 'ghost':
68
+ return styles.buttonGhost;
69
+ case 'neutral':
70
+ default:
71
+ return styles.buttonNeutral;
72
+ }
73
+ }
74
+ function buttonTextStyleFor(variant) {
75
+ switch (variant) {
76
+ case 'primary':
77
+ return styles.buttonTextPrimary;
78
+ case 'destructive':
79
+ return styles.buttonTextDestructive;
80
+ case 'ghost':
81
+ return styles.buttonTextGhost;
82
+ case 'neutral':
83
+ default:
84
+ return styles.buttonTextNeutral;
85
+ }
86
+ }
87
+ const styles = react_native_1.StyleSheet.create({
88
+ backdrop: {
89
+ flex: 1,
90
+ backgroundColor: 'rgba(0,0,0,0.96)',
91
+ paddingTop: 56,
92
+ paddingBottom: 32,
93
+ paddingHorizontal: 16,
94
+ },
95
+ topBar: {
96
+ flexDirection: 'row',
97
+ alignItems: 'center',
98
+ marginBottom: 12,
99
+ },
100
+ topBarSpacer: {
101
+ width: 44,
102
+ },
103
+ title: {
104
+ flex: 1,
105
+ color: '#ffffff',
106
+ fontSize: 16,
107
+ fontWeight: '600',
108
+ textAlign: 'center',
109
+ },
110
+ closeButton: {
111
+ width: 44,
112
+ height: 44,
113
+ borderRadius: 22,
114
+ backgroundColor: 'rgba(255,255,255,0.12)',
115
+ alignItems: 'center',
116
+ justifyContent: 'center',
117
+ },
118
+ closeText: {
119
+ color: '#ffffff',
120
+ fontSize: 28,
121
+ fontWeight: '300',
122
+ lineHeight: 32,
123
+ marginTop: -2,
124
+ },
125
+ imageWrapper: {
126
+ flex: 1,
127
+ alignItems: 'center',
128
+ justifyContent: 'center',
129
+ },
130
+ image: {
131
+ width: '100%',
132
+ maxHeight: '100%',
133
+ backgroundColor: '#111',
134
+ borderRadius: 4,
135
+ },
136
+ buttonRow: {
137
+ flexDirection: 'row',
138
+ justifyContent: 'space-between',
139
+ alignItems: 'center',
140
+ marginTop: 16,
141
+ gap: 12,
142
+ },
143
+ button: {
144
+ flex: 1,
145
+ paddingVertical: 14,
146
+ borderRadius: 10,
147
+ alignItems: 'center',
148
+ justifyContent: 'center',
149
+ },
150
+ buttonDisabled: {
151
+ opacity: 0.45,
152
+ },
153
+ buttonPrimary: {
154
+ backgroundColor: '#34C759',
155
+ },
156
+ buttonNeutral: {
157
+ backgroundColor: 'rgba(255,255,255,0.12)',
158
+ },
159
+ buttonGhost: {
160
+ backgroundColor: 'transparent',
161
+ borderWidth: 1,
162
+ borderColor: 'rgba(255,255,255,0.3)',
163
+ },
164
+ buttonDestructive: {
165
+ backgroundColor: '#FF3B30',
166
+ },
167
+ buttonText: {
168
+ fontSize: 14,
169
+ fontWeight: '600',
170
+ },
171
+ buttonTextPrimary: {
172
+ color: '#ffffff',
173
+ fontWeight: '700',
174
+ },
175
+ buttonTextNeutral: {
176
+ color: '#ffffff',
177
+ },
178
+ buttonTextGhost: {
179
+ color: '#ffffff',
180
+ opacity: 0.9,
181
+ fontWeight: '500',
182
+ },
183
+ buttonTextDestructive: {
184
+ color: '#ffffff',
185
+ fontWeight: '700',
186
+ },
187
+ });
188
+ //# sourceMappingURL=CapturePreview.js.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * CaptureStatusOverlay — screen-level visual feedback for the
3
+ * panorama capture lifecycle.
4
+ *
5
+ * Lives in the SDK because the existing shutter-button colour change
6
+ * is hidden by the user's finger during a hold. An overlay above
7
+ * the preview is the only reliable channel for "you ARE recording
8
+ * right now" feedback.
9
+ *
10
+ * ┌──────────────────────────────────────────────────┐
11
+ * │ ● REC Hold steady, pan slowly… │ ← banner
12
+ * ├──────────────────────────────────────────────────┤
13
+ * │ ┌──────────────────────────────────────────────┐ │
14
+ * │ │ │ │
15
+ * │ │ ⬛ red glow border │ │
16
+ * │ │ around the preview │ │
17
+ * │ │ │ │
18
+ * │ └──────────────────────────────────────────────┘ │
19
+ * └──────────────────────────────────────────────────┘
20
+ *
21
+ * The component is intentionally pure-presentational: it takes a
22
+ * `phase` prop and renders the matching UI. Recording vs stitching
23
+ * vs idle is the host's source of truth — typically derived from
24
+ * `useVideoCapture().state` and a local "isStitching" boolean.
25
+ *
26
+ * The overlay renders nothing in `idle` so the host can render it
27
+ * unconditionally without conditional layout shifts.
28
+ */
29
+ import React from 'react';
30
+ import { type StyleProp, type ViewStyle } from 'react-native';
31
+ export type CaptureStatusPhase = 'idle' | 'recording' | 'stitching';
32
+ export interface CaptureStatusOverlayProps {
33
+ /**
34
+ * Current phase. `idle` renders nothing. `recording` shows the
35
+ * REC banner + red glowing border. `stitching` swaps to a neutral
36
+ * "Stitching..." banner with no border (recording is over; UI
37
+ * cue should de-escalate).
38
+ */
39
+ phase: CaptureStatusPhase;
40
+ /**
41
+ * Optional override for the recording-phase message. Defaults to
42
+ * "Hold steady — pan slowly". Useful if the host wants direction
43
+ * hints (e.g. "Pan down across the rack") for a specific audit
44
+ * type.
45
+ */
46
+ recordingMessage?: string;
47
+ /**
48
+ * Optional override for the stitching-phase message. Defaults to
49
+ * "Stitching panorama…".
50
+ */
51
+ stitchingMessage?: string;
52
+ /**
53
+ * If set, the recording-phase banner shows a live countdown
54
+ * ("REC 4s left") computed against this value. Set to the
55
+ * shutter's `maxHoldMs` so the user can see how long they have
56
+ * left before the auto-stop fires. Pair with a fresh value
57
+ * each time recording starts so the timer resets per capture.
58
+ *
59
+ * `recordingStartedAt` is the timestamp (Date.now()) when the
60
+ * recording phase began — required for the countdown math.
61
+ */
62
+ countdownMs?: number;
63
+ recordingStartedAt?: number;
64
+ /**
65
+ * Top inset to offset the banner below the status bar / notch.
66
+ * Defaults to 0 — host apps using `react-native-safe-area-context`
67
+ * should pass `insets.top` here so the banner doesn't disappear
68
+ * behind the notch.
69
+ */
70
+ topInset?: number;
71
+ /** Outer style passthrough. */
72
+ style?: StyleProp<ViewStyle>;
73
+ }
74
+ export declare function CaptureStatusOverlay({ phase, recordingMessage, stitchingMessage, countdownMs, recordingStartedAt, topInset, style, }: CaptureStatusOverlayProps): React.JSX.Element | null;
75
+ //# sourceMappingURL=CaptureStatusOverlay.d.ts.map