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.
- package/CHANGELOG.md +96 -0
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +189 -0
- package/RNImageStitcher.podspec +76 -0
- package/android/build.gradle +224 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/cpp/CMakeLists.txt +124 -0
- package/android/src/main/cpp/image_stitcher_jni.cpp +145 -0
- package/android/src/main/cpp/keyframe_gate_jni.cpp +204 -0
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +426 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +960 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +2371 -0
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +256 -0
- package/android/src/main/java/io/imagestitcher/rn/QualityChecker.kt +167 -0
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +39 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +558 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraViewManager.kt +35 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +784 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/BackgroundRenderer.kt +176 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/ShaderUtil.kt +67 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +201 -0
- package/cpp/ar_frame_pose.h +63 -0
- package/cpp/keyframe_gate.cpp +927 -0
- package/cpp/keyframe_gate.hpp +240 -0
- package/cpp/stitcher.cpp +2207 -0
- package/cpp/stitcher.hpp +275 -0
- package/dist/ar/useARSession.d.ts +102 -0
- package/dist/ar/useARSession.js +133 -0
- package/dist/camera/ARCameraView.d.ts +93 -0
- package/dist/camera/ARCameraView.js +170 -0
- package/dist/camera/Camera.d.ts +134 -0
- package/dist/camera/Camera.js +688 -0
- package/dist/camera/CameraShutter.d.ts +80 -0
- package/dist/camera/CameraShutter.js +237 -0
- package/dist/camera/CameraView.d.ts +65 -0
- package/dist/camera/CameraView.js +117 -0
- package/dist/camera/CaptureControlsBar.d.ts +87 -0
- package/dist/camera/CaptureControlsBar.js +82 -0
- package/dist/camera/CaptureHeader.d.ts +62 -0
- package/dist/camera/CaptureHeader.js +81 -0
- package/dist/camera/CapturePreview.d.ts +70 -0
- package/dist/camera/CapturePreview.js +188 -0
- package/dist/camera/CaptureStatusOverlay.d.ts +75 -0
- package/dist/camera/CaptureStatusOverlay.js +326 -0
- package/dist/camera/CaptureThumbnailStrip.d.ts +87 -0
- package/dist/camera/CaptureThumbnailStrip.js +177 -0
- package/dist/camera/IncrementalPanGuide.d.ts +83 -0
- package/dist/camera/IncrementalPanGuide.js +267 -0
- package/dist/camera/PanoramaBandOverlay.d.ts +107 -0
- package/dist/camera/PanoramaBandOverlay.js +399 -0
- package/dist/camera/PanoramaConfirmModal.d.ts +57 -0
- package/dist/camera/PanoramaConfirmModal.js +128 -0
- package/dist/camera/PanoramaGuidance.d.ts +79 -0
- package/dist/camera/PanoramaGuidance.js +246 -0
- package/dist/camera/PanoramaSettingsModal.d.ts +311 -0
- package/dist/camera/PanoramaSettingsModal.js +611 -0
- package/dist/camera/ViewportCropOverlay.d.ts +46 -0
- package/dist/camera/ViewportCropOverlay.js +67 -0
- package/dist/camera/useCapture.d.ts +111 -0
- package/dist/camera/useCapture.js +160 -0
- package/dist/camera/useDeviceOrientation.d.ts +48 -0
- package/dist/camera/useDeviceOrientation.js +131 -0
- package/dist/camera/useVideoCapture.d.ts +79 -0
- package/dist/camera/useVideoCapture.js +151 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +39 -0
- package/dist/quality/normaliseOrientation.d.ts +36 -0
- package/dist/quality/normaliseOrientation.js +62 -0
- package/dist/quality/runQualityCheck.d.ts +41 -0
- package/dist/quality/runQualityCheck.js +98 -0
- package/dist/sensors/useIMUTranslationGate.d.ts +70 -0
- package/dist/sensors/useIMUTranslationGate.js +235 -0
- package/dist/stitching/IncrementalStitcherView.d.ts +41 -0
- package/dist/stitching/IncrementalStitcherView.js +157 -0
- package/dist/stitching/incremental.d.ts +930 -0
- package/dist/stitching/incremental.js +133 -0
- package/dist/stitching/stitchFrames.d.ts +55 -0
- package/dist/stitching/stitchFrames.js +56 -0
- package/dist/stitching/stitchVideo.d.ts +119 -0
- package/dist/stitching/stitchVideo.js +57 -0
- package/dist/stitching/useIncrementalJSDriver.d.ts +74 -0
- package/dist/stitching/useIncrementalJSDriver.js +199 -0
- package/dist/stitching/useIncrementalStitcher.d.ts +58 -0
- package/dist/stitching/useIncrementalStitcher.js +172 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +15 -0
- package/ios/Package.swift +72 -0
- package/ios/Sources/RNImageStitcher/ARCameraViewManager.m +33 -0
- package/ios/Sources/RNImageStitcher/ARCameraViewManager.swift +40 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.m +55 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +149 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +2727 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +85 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +625 -0
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +328 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +141 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +278 -0
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +473 -0
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +1326 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +97 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +296 -0
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +103 -0
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +3285 -0
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +238 -0
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +1880 -0
- package/ios/Sources/RNImageStitcher/QualityChecker.swift +252 -0
- package/ios/Sources/RNImageStitcher/QualityCheckerBridge.m +26 -0
- package/ios/Sources/RNImageStitcher/QualityCheckerBridge.swift +72 -0
- package/ios/Sources/RNImageStitcher/RNSARCameraView.swift +114 -0
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +1111 -0
- package/ios/Sources/RNImageStitcher/Stitcher.swift +243 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.m +28 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.swift +246 -0
- package/package.json +73 -0
- package/react-native.config.js +34 -0
- package/scripts/opencv-version.txt +1 -0
- package/scripts/postinstall-fetch-binaries.js +286 -0
- package/src/ar/useARSession.ts +210 -0
- package/src/camera/.gitkeep +0 -0
- package/src/camera/ARCameraView.tsx +256 -0
- package/src/camera/Camera.tsx +1053 -0
- package/src/camera/CameraShutter.tsx +292 -0
- package/src/camera/CameraView.tsx +157 -0
- package/src/camera/CaptureControlsBar.tsx +204 -0
- package/src/camera/CaptureHeader.tsx +184 -0
- package/src/camera/CapturePreview.tsx +318 -0
- package/src/camera/CaptureStatusOverlay.tsx +391 -0
- package/src/camera/CaptureThumbnailStrip.tsx +277 -0
- package/src/camera/IncrementalPanGuide.tsx +328 -0
- package/src/camera/PanoramaBandOverlay.tsx +498 -0
- package/src/camera/PanoramaConfirmModal.tsx +206 -0
- package/src/camera/PanoramaGuidance.tsx +327 -0
- package/src/camera/PanoramaSettingsModal.tsx +1357 -0
- package/src/camera/ViewportCropOverlay.tsx +81 -0
- package/src/camera/useCapture.ts +279 -0
- package/src/camera/useDeviceOrientation.ts +140 -0
- package/src/camera/useVideoCapture.ts +236 -0
- package/src/index.ts +53 -0
- package/src/quality/.gitkeep +0 -0
- package/src/quality/normaliseOrientation.ts +79 -0
- package/src/quality/runQualityCheck.ts +131 -0
- package/src/sensors/useIMUTranslationGate.ts +347 -0
- package/src/stitching/.gitkeep +0 -0
- package/src/stitching/IncrementalStitcherView.tsx +198 -0
- package/src/stitching/incremental.ts +1021 -0
- package/src/stitching/stitchFrames.ts +88 -0
- package/src/stitching/stitchVideo.ts +153 -0
- package/src/stitching/useIncrementalJSDriver.ts +273 -0
- package/src/stitching/useIncrementalStitcher.ts +252 -0
- 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
|