react-native-image-stitcher 0.13.0 → 0.14.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 +105 -0
- package/README.md +33 -17
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +33 -5
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +73 -1
- package/dist/camera/Camera.d.ts +71 -16
- package/dist/camera/Camera.js +167 -51
- package/dist/camera/CameraView.d.ts +6 -0
- package/dist/camera/CameraView.js +2 -2
- package/dist/camera/CaptureHeader.js +39 -16
- package/dist/camera/CapturePreview.js +13 -1
- package/dist/camera/CaptureThumbnailStrip.d.ts +25 -1
- package/dist/camera/CaptureThumbnailStrip.js +17 -4
- package/dist/camera/PanoramaBandOverlay.d.ts +76 -0
- package/dist/camera/PanoramaBandOverlay.js +90 -33
- package/dist/camera/PanoramaConfirmModal.js +11 -1
- package/dist/camera/selectCaptureDevice.d.ts +93 -0
- package/dist/camera/selectCaptureDevice.js +131 -0
- package/dist/camera/useCapture.d.ts +40 -0
- package/dist/camera/useCapture.js +50 -12
- package/dist/camera/useContentRotation.d.ts +99 -0
- package/dist/camera/useContentRotation.js +124 -0
- package/dist/index.d.ts +1 -3
- package/dist/index.js +6 -5
- package/package.json +1 -1
- package/src/camera/Camera.tsx +281 -118
- package/src/camera/CameraView.tsx +9 -0
- package/src/camera/CaptureHeader.tsx +39 -16
- package/src/camera/CapturePreview.tsx +12 -0
- package/src/camera/CaptureThumbnailStrip.tsx +44 -4
- package/src/camera/PanoramaBandOverlay.tsx +97 -35
- package/src/camera/PanoramaConfirmModal.tsx +10 -0
- package/src/camera/__tests__/bandThumbRotation.test.ts +120 -0
- package/src/camera/__tests__/homeIndicatorEdge.test.ts +116 -0
- package/src/camera/__tests__/selectCaptureDevice.test.ts +177 -0
- package/src/camera/__tests__/useContentRotation.test.ts +89 -0
- package/src/camera/selectCaptureDevice.ts +187 -0
- package/src/camera/useCapture.ts +99 -11
- package/src/camera/useContentRotation.ts +149 -0
- package/src/index.ts +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,111 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [0.14.0] — 2026-06-01
|
|
20
|
+
|
|
21
|
+
### Fixed — Android AR single-photo orientation (landscape was sideways)
|
|
22
|
+
|
|
23
|
+
Android AR `takePhoto` baked the wrong rotation into landscape captures
|
|
24
|
+
under a portrait-locked host: it derived the EXIF orientation from the
|
|
25
|
+
window display rotation (`WindowManager.defaultDisplay.rotation`), which
|
|
26
|
+
stays `ROTATION_0` when the activity is portrait-locked regardless of how
|
|
27
|
+
the device is physically held — so a landscape photo got a portrait EXIF
|
|
28
|
+
tag and came out 90° CW. The JS layer already passed the gyro device
|
|
29
|
+
orientation to `RNSARSession.takePhoto` (since v0.12), and iOS consumed
|
|
30
|
+
it, but the Android native side dropped it. Now Android threads the
|
|
31
|
+
device orientation through `takePhoto → requestTakePhoto → encodeToJpeg`,
|
|
32
|
+
mapping it to the correct `Surface.ROTATION_*` / EXIF tag. iOS unchanged
|
|
33
|
+
(already correct). Verified on-device (Samsung A35) in both landscape
|
|
34
|
+
orientations.
|
|
35
|
+
|
|
36
|
+
### Added — `captureSources` constraint prop
|
|
37
|
+
|
|
38
|
+
`<Camera>` gains `captureSources?: 'ar' | 'non-ar' | 'both'` (default
|
|
39
|
+
`'both'`) — a constraint on which capture sources the host allows, layered
|
|
40
|
+
over `defaultCaptureSource` (which picks the initial source within it):
|
|
41
|
+
|
|
42
|
+
- `'both'` — AR + non-AR; the runtime AR toggle is shown (unchanged
|
|
43
|
+
default behaviour).
|
|
44
|
+
- `'ar'` — AR only; the AR toggle is hidden (nothing to switch to) and
|
|
45
|
+
the 0.5×/1× lens chooser is hidden (ARKit/ARCore can't use the
|
|
46
|
+
ultra-wide), keeping capture on the AR-capable 1× lens.
|
|
47
|
+
- `'non-ar'`— non-AR only; the AR toggle is hidden, the lens chooser stays.
|
|
48
|
+
|
|
49
|
+
A single-source constraint overrides a conflicting `defaultCaptureSource`.
|
|
50
|
+
Exported type: `CaptureSourcesMode`. Verified on-device (A35) across all
|
|
51
|
+
three modes.
|
|
52
|
+
|
|
53
|
+
### Fixed — capability-aware lens selection (ultra-wide + flash on 0.5×)
|
|
54
|
+
|
|
55
|
+
`<Camera>` now selects the back camera device by real capability instead
|
|
56
|
+
of requesting a single physical lens per zoom level. `selectCaptureDevice`:
|
|
57
|
+
|
|
58
|
+
- **Prefers a multi-cam device** that spans wide + ultra-wide (lens
|
|
59
|
+
switched via `zoom`; torch available on every lens). On devices that
|
|
60
|
+
expose such a device (e.g. iPhone 16 Pro — verified `multicam`), this
|
|
61
|
+
fixes the user-reported "0.5× shows the wide-angle FOV" bug AND makes
|
|
62
|
+
flash work on 0.5× (the mounted multi-cam device carries the torch).
|
|
63
|
+
- **Falls back to a standalone ultra-wide** device-swap where no multi-cam
|
|
64
|
+
device exists (e.g. Samsung A35 — verified `standalone-uw`; vision-camera
|
|
65
|
+
surfaces the physical cameras separately there). 0.5× still shows the
|
|
66
|
+
ultra-wide FOV; flash hides because that standalone device is torchless.
|
|
67
|
+
|
|
68
|
+
`has0_5x` is now derived from the real device inventory (was hardcoded
|
|
69
|
+
`true`), so the lens chooser hides on wide-only hardware. 13 unit tests
|
|
70
|
+
cover the selection matrix incl. both edge cases (ultra-wide only in a
|
|
71
|
+
multi-cam group; ultra-wide only standalone).
|
|
72
|
+
|
|
73
|
+
Verified on-device: iPhone 16 Pro (multicam — 0.5× FOV + flash both work)
|
|
74
|
+
and Samsung A35 (standalone-uw — 0.5× FOV works, flash correctly hidden).
|
|
75
|
+
|
|
76
|
+
### Added — Android portrait lock (SDK-enforced)
|
|
77
|
+
|
|
78
|
+
`<Camera>` now locks its host Activity to portrait on Android while
|
|
79
|
+
mounted, via `Activity.setRequestedOrientation`, **regardless of the
|
|
80
|
+
host app's `AndroidManifest` `screenOrientation`**. A landscape or
|
|
81
|
+
unlocked host still gets a portrait camera screen. The Activity's
|
|
82
|
+
prior orientation is captured on mount and restored on unmount.
|
|
83
|
+
Implemented in the native `RNSARSession` module (`lockPortrait()` /
|
|
84
|
+
`unlockOrientation()`) and driven from a `<Camera>` mount effect, so
|
|
85
|
+
it covers both the AR (ARCore) and non-AR (vision-camera) paths.
|
|
86
|
+
There is no opt-out — Android capture is portrait-only by design.
|
|
87
|
+
|
|
88
|
+
iOS is intentionally unchanged: supported orientations remain owned by
|
|
89
|
+
the host `Info.plist`. **Portrait is the recommended configuration on
|
|
90
|
+
both platforms; landscape is supported on iOS** for hosts that need it.
|
|
91
|
+
|
|
92
|
+
### Fixed — landscape preview + thumbnail orientation (non-locked iOS)
|
|
93
|
+
|
|
94
|
+
- **Preview squish / sideways** under a non-locked host was caused by
|
|
95
|
+
an in-development `patch-package` patch to vision-camera's
|
|
96
|
+
`OrientationManager` (both `.kt` and `.swift`) that derived the
|
|
97
|
+
PREVIEW orientation from the accelerometer instead of the interface
|
|
98
|
+
orientation. In a portrait host held landscape this forced a
|
|
99
|
+
landscape preview into a portrait surface. The patch was removed and
|
|
100
|
+
vision-camera restored to pristine on both platforms.
|
|
101
|
+
- **Band keyframe thumbnails rotated 90°**: the per-keyframe tiles in
|
|
102
|
+
`PanoramaBandOverlay` were double-rotated — the saved `keyframe-N.jpg`
|
|
103
|
+
is sensor-native landscape + EXIF Orientation 6, which `<Image>`
|
|
104
|
+
already auto-rotates, so the extra JS transform was redundant in the
|
|
105
|
+
portrait-locked (`vertical=false`) path. The transform is now applied
|
|
106
|
+
only in the `vertical=true` (non-locked landscape) path.
|
|
107
|
+
- **Stitched-preview / confirm modals stuck portrait**: `CapturePreview`
|
|
108
|
+
and `PanoramaConfirmModal` were missing `supportedOrientations`
|
|
109
|
+
(RN's iOS `<Modal>` defaults to portrait-only). Both now declare all
|
|
110
|
+
four, matching `OrientationDriftModal` + `PanoramaSettingsModal`.
|
|
111
|
+
- **Idle thumbnail strip horizontal in landscape**: `CaptureThumbnailStrip`
|
|
112
|
+
gained a `vertical` prop (wired from the same `isSideEdge` signal as
|
|
113
|
+
the band) so the idle strip stacks vertically along the home-indicator
|
|
114
|
+
edge under a non-locked host instead of running across the screen.
|
|
115
|
+
|
|
116
|
+
### Removed — pan-guidance overlays no longer public
|
|
117
|
+
|
|
118
|
+
`IncrementalPanGuide` (drift marker) and `PanoramaGuidance` (pan-speed
|
|
119
|
+
pill) are no longer exported, and the `panGuide` / `panoramaGuidance`
|
|
120
|
+
props were removed from `<Camera>`. The components remain in the tree
|
|
121
|
+
as internal-only code (not rendered). Hosts that were passing these
|
|
122
|
+
props should remove them.
|
|
123
|
+
|
|
19
124
|
## [0.13.0] — 2026-05-29
|
|
20
125
|
|
|
21
126
|
### Added — Layer-2 components absorbed into `<Camera>` (opt-out)
|
package/README.md
CHANGED
|
@@ -104,7 +104,11 @@ export function CaptureScreen() {
|
|
|
104
104
|
|
|
105
105
|
## `<Camera>` props (summary)
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
> **Full reference:** [`docs/camera-component.md`](docs/camera-component.md)
|
|
108
|
+
> covers every prop with purpose, default, behaviour notes, the
|
|
109
|
+
> `CameraCaptureResult` / `CameraError` shapes, orientation
|
|
110
|
+
> behaviour, and common compositions. This README summary lists
|
|
111
|
+
> the highlights only.
|
|
108
112
|
|
|
109
113
|
### Initial values (uncontrolled — read once at mount)
|
|
110
114
|
|
|
@@ -142,22 +146,34 @@ See `src/camera/Camera.tsx` for the full TSDoc. Highlights:
|
|
|
142
146
|
|
|
143
147
|
## Orientation support
|
|
144
148
|
|
|
145
|
-
`<Camera>`
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
149
|
+
> **Recommended: portrait.** `<Camera>` is designed and tuned for
|
|
150
|
+
> portrait capture, and that is the recommended way to use it on both
|
|
151
|
+
> platforms. Landscape is supported on iOS for hosts that need it
|
|
152
|
+
> (see below); on Android the camera is always portrait.
|
|
153
|
+
|
|
154
|
+
**Android — always portrait (SDK-enforced).** On Android `<Camera>`
|
|
155
|
+
locks its host Activity to portrait while mounted (via
|
|
156
|
+
`Activity.setRequestedOrientation`), **regardless of the host app's
|
|
157
|
+
manifest** — even a fully landscape or unlocked host gets a portrait
|
|
158
|
+
camera screen. The prior orientation is restored when `<Camera>`
|
|
159
|
+
unmounts. No host setup is required and there is no opt-out: Android
|
|
160
|
+
capture is portrait-only by design.
|
|
161
|
+
|
|
162
|
+
**iOS — portrait recommended, landscape supported.** iOS supported
|
|
163
|
+
orientations are owned by the host's `Info.plist`
|
|
164
|
+
(`UISupportedInterfaceOrientations`); the SDK does not override them.
|
|
165
|
+
|
|
166
|
+
- *Portrait-only host* (Info.plist = Portrait — **recommended**): the
|
|
167
|
+
screen stays portrait; the SDK uses sensor-derived orientation for
|
|
168
|
+
capture-mode selection and overlay layout. Simplest configuration.
|
|
169
|
+
- *Non-locked host* (Info.plist supports all 4 — supported for apps
|
|
170
|
+
with other landscape-friendly screens): the screen rotates with the
|
|
171
|
+
device. `<Camera>`'s controls (shutter, lens chip, AR toggle) and
|
|
172
|
+
the live thumbnail strip/band anchor to the home-indicator edge so
|
|
173
|
+
they stay within thumb reach regardless of tilt — matching iOS
|
|
174
|
+
Camera's behaviour. The orientation-aware logic combines
|
|
175
|
+
`useWindowDimensions()` (JS-layout) with `useDeviceOrientation()`
|
|
176
|
+
(sensor) to compute the correct anchor.
|
|
161
177
|
|
|
162
178
|
**Mid-capture rotation safety** — the incremental engine doesn't
|
|
163
179
|
support cross-orientation captures (a portrait capture's keyframes
|
|
@@ -89,6 +89,14 @@ class RNSARCameraView @JvmOverloads constructor(
|
|
|
89
89
|
internal data class TakePhotoRequest(
|
|
90
90
|
val outputPath: String,
|
|
91
91
|
val quality: Int,
|
|
92
|
+
// v0.13.2 — physical device orientation at capture time, from the
|
|
93
|
+
// JS `useDeviceOrientation()` hook (one of 'portrait' /
|
|
94
|
+
// 'portrait-upside-down' / 'landscape-left' / 'landscape-right').
|
|
95
|
+
// Used INSTEAD of the window display rotation so AR photos come
|
|
96
|
+
// out upright even under a PORTRAIT-LOCKED host (where the window
|
|
97
|
+
// rotation is always ROTATION_0 regardless of how the device is
|
|
98
|
+
// held — the cause of the "landscape AR photo is sideways" bug).
|
|
99
|
+
val orientation: String,
|
|
92
100
|
val promise: com.facebook.react.bridge.Promise,
|
|
93
101
|
)
|
|
94
102
|
private val pendingTakePhoto =
|
|
@@ -101,9 +109,10 @@ class RNSARCameraView @JvmOverloads constructor(
|
|
|
101
109
|
internal fun requestTakePhoto(
|
|
102
110
|
outputPath: String,
|
|
103
111
|
quality: Int,
|
|
112
|
+
orientation: String,
|
|
104
113
|
promise: com.facebook.react.bridge.Promise,
|
|
105
114
|
) {
|
|
106
|
-
val req = TakePhotoRequest(outputPath, quality, promise)
|
|
115
|
+
val req = TakePhotoRequest(outputPath, quality, orientation, promise)
|
|
107
116
|
val previous = pendingTakePhoto.getAndSet(req)
|
|
108
117
|
previous?.promise?.reject(
|
|
109
118
|
"ar-photo-superseded",
|
|
@@ -339,10 +348,15 @@ class RNSARCameraView @JvmOverloads constructor(
|
|
|
339
348
|
image,
|
|
340
349
|
req.outputPath,
|
|
341
350
|
jpegQuality = req.quality.coerceIn(1, 100),
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
351
|
+
// v0.13.2 — derive the encode rotation from the PHYSICAL
|
|
352
|
+
// device orientation (JS gyro), not the window display
|
|
353
|
+
// rotation. Under a portrait-locked host the window stays
|
|
354
|
+
// ROTATION_0 regardless of how the device is held, so the
|
|
355
|
+
// old `lastDisplayRotation` path baked a portrait EXIF tag
|
|
356
|
+
// onto landscape captures → sideways photo. The
|
|
357
|
+
// device-orientation → Surface.ROTATION_* mapping below
|
|
358
|
+
// feeds encodeToJpeg's existing EXIF table.
|
|
359
|
+
displayRotation = deviceOrientationToSurfaceRotation(req.orientation),
|
|
346
360
|
)
|
|
347
361
|
if (written == null) {
|
|
348
362
|
req.promise.reject(
|
|
@@ -632,6 +646,20 @@ class RNSARCameraView @JvmOverloads constructor(
|
|
|
632
646
|
)
|
|
633
647
|
}
|
|
634
648
|
|
|
649
|
+
/// v0.13.2 — map the JS physical device orientation to the
|
|
650
|
+
/// `Surface.ROTATION_*` value `YuvImageConverter.encodeToJpeg`
|
|
651
|
+
/// expects. Mirrors the equivalence documented in encodeToJpeg's
|
|
652
|
+
/// EXIF table (ROTATION_0=portrait, _90=landscape-left,
|
|
653
|
+
/// _180=portrait-upside-down, _270=landscape-right). Unknown /
|
|
654
|
+
/// missing → portrait (the safe pre-v0.12 default).
|
|
655
|
+
private fun deviceOrientationToSurfaceRotation(orientation: String): Int =
|
|
656
|
+
when (orientation) {
|
|
657
|
+
"landscape-left" -> Surface.ROTATION_90
|
|
658
|
+
"portrait-upside-down" -> Surface.ROTATION_180
|
|
659
|
+
"landscape-right" -> Surface.ROTATION_270
|
|
660
|
+
else -> Surface.ROTATION_0 // "portrait" + fallback
|
|
661
|
+
}
|
|
662
|
+
|
|
635
663
|
private fun applyDisplayGeometry() {
|
|
636
664
|
val session = sessionRef.get() ?: return
|
|
637
665
|
val rotation = (context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager)
|
|
@@ -3,6 +3,7 @@ package io.imagestitcher.rn
|
|
|
3
3
|
|
|
4
4
|
import android.app.Activity
|
|
5
5
|
import android.content.Context
|
|
6
|
+
import android.content.pm.ActivityInfo
|
|
6
7
|
import android.util.Log
|
|
7
8
|
import com.facebook.react.bridge.Arguments
|
|
8
9
|
import com.facebook.react.bridge.Promise
|
|
@@ -58,6 +59,65 @@ class RNSARSession(reactContext: ReactApplicationContext)
|
|
|
58
59
|
private val poseLog = mutableListOf<RNSARFramePose>()
|
|
59
60
|
private val poseLogLock = ReentrantReadWriteLock()
|
|
60
61
|
|
|
62
|
+
// ── v0.13.1 — Android <Camera> portrait lock ────────────────────
|
|
63
|
+
//
|
|
64
|
+
// Unlike iOS (where supported orientations are a static Info.plist
|
|
65
|
+
// declaration the app can't override per-view at runtime), Android
|
|
66
|
+
// lets a view force its host Activity's orientation via
|
|
67
|
+
// `Activity.requestedOrientation`. The SDK's `<Camera>` uses this
|
|
68
|
+
// to guarantee a portrait capture surface regardless of the host
|
|
69
|
+
// app's manifest — even a fully landscape/unlocked host gets a
|
|
70
|
+
// portrait camera while `<Camera>` is mounted.
|
|
71
|
+
//
|
|
72
|
+
// `lockPortrait()` is called from `Camera.tsx`'s mount effect and
|
|
73
|
+
// covers BOTH capture paths (AR ARCore view + non-AR vision-camera)
|
|
74
|
+
// because the lock lives on the Activity, not on either camera view.
|
|
75
|
+
// `unlockOrientation()` (mount-effect cleanup) restores the EXACT
|
|
76
|
+
// orientation the Activity had before we locked, so hosts with
|
|
77
|
+
// mixed-orientation screens get their prior setting back rather than
|
|
78
|
+
// a generic default.
|
|
79
|
+
//
|
|
80
|
+
// SCREEN_ORIENTATION_UNSET (-2) is our "nothing captured yet"
|
|
81
|
+
// sentinel; we never pass it to setRequestedOrientation.
|
|
82
|
+
private var priorRequestedOrientation: Int = ORIENTATION_UNSET
|
|
83
|
+
|
|
84
|
+
@ReactMethod
|
|
85
|
+
fun lockPortrait() {
|
|
86
|
+
val activity: Activity = reactApplicationContext.currentActivity ?: run {
|
|
87
|
+
Log.w(TAG, "lockPortrait: no current activity — skipping")
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
activity.runOnUiThread {
|
|
91
|
+
// Capture the prior value ONCE (first lock wins). Guards
|
|
92
|
+
// against a remount double-capturing our own portrait value
|
|
93
|
+
// and losing the host's real prior orientation.
|
|
94
|
+
if (priorRequestedOrientation == ORIENTATION_UNSET) {
|
|
95
|
+
priorRequestedOrientation = activity.requestedOrientation
|
|
96
|
+
}
|
|
97
|
+
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@ReactMethod
|
|
102
|
+
fun unlockOrientation() {
|
|
103
|
+
val activity: Activity = reactApplicationContext.currentActivity ?: run {
|
|
104
|
+
Log.w(TAG, "unlockOrientation: no current activity — skipping")
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
activity.runOnUiThread {
|
|
108
|
+
if (priorRequestedOrientation != ORIENTATION_UNSET) {
|
|
109
|
+
activity.requestedOrientation = priorRequestedOrientation
|
|
110
|
+
priorRequestedOrientation = ORIENTATION_UNSET
|
|
111
|
+
} else {
|
|
112
|
+
// No capture on record (lock never ran or already
|
|
113
|
+
// restored) — fall back to UNSPECIFIED so we don't pin
|
|
114
|
+
// the host to whatever we last set.
|
|
115
|
+
activity.requestedOrientation =
|
|
116
|
+
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
61
121
|
@ReactMethod
|
|
62
122
|
fun isSupported(promise: Promise) {
|
|
63
123
|
// `checkAvailability` can return UNKNOWN_CHECKING if the
|
|
@@ -395,6 +455,13 @@ class RNSARSession(reactContext: ReactApplicationContext)
|
|
|
395
455
|
}
|
|
396
456
|
val rawPath = if (options.hasKey("path")) options.getString("path") ?: "" else ""
|
|
397
457
|
val quality = if (options.hasKey("quality")) options.getInt("quality") else 90
|
|
458
|
+
// v0.13.2 — physical device orientation from JS (useDeviceOrientation).
|
|
459
|
+
// Drives the saved JPEG's rotation so landscape AR captures are
|
|
460
|
+
// upright even under a portrait-locked host. Defaults to
|
|
461
|
+
// 'portrait' (pre-v0.12 behaviour) when the host omits it.
|
|
462
|
+
val orientation =
|
|
463
|
+
if (options.hasKey("orientation")) options.getString("orientation") ?: "portrait"
|
|
464
|
+
else "portrait"
|
|
398
465
|
val resolvedPath: String = if (rawPath.isNotEmpty()) {
|
|
399
466
|
rawPath
|
|
400
467
|
} else {
|
|
@@ -404,7 +471,7 @@ class RNSARSession(reactContext: ReactApplicationContext)
|
|
|
404
471
|
"RNImageStitcher-ar-${java.util.UUID.randomUUID()}.jpg",
|
|
405
472
|
).absolutePath
|
|
406
473
|
}
|
|
407
|
-
view.requestTakePhoto(resolvedPath, quality, promise)
|
|
474
|
+
view.requestTakePhoto(resolvedPath, quality, orientation, promise)
|
|
408
475
|
}
|
|
409
476
|
|
|
410
477
|
@ReactMethod
|
|
@@ -776,6 +843,11 @@ class RNSARSession(reactContext: ReactApplicationContext)
|
|
|
776
843
|
private const val TAG = "RNSARSession"
|
|
777
844
|
private const val MAX_POSE_LOG = 600 // ~10 s @ 60Hz
|
|
778
845
|
|
|
846
|
+
// Sentinel for "no prior Activity orientation captured yet".
|
|
847
|
+
// Distinct from any real ActivityInfo.SCREEN_ORIENTATION_*
|
|
848
|
+
// value (those are >= -1); -2 is unused by the framework.
|
|
849
|
+
private const val ORIENTATION_UNSET = -2
|
|
850
|
+
|
|
779
851
|
/**
|
|
780
852
|
* Convenience accessor for the AR camera view (in Phase 4.4)
|
|
781
853
|
* to reach the singleton-installed module instance. We use
|
package/dist/camera/Camera.d.ts
CHANGED
|
@@ -44,7 +44,19 @@ import type { DrawableFrameProcessor, ReadonlyFrameProcessor } from 'react-nativ
|
|
|
44
44
|
import { type CaptureHeaderProps } from './CaptureHeader';
|
|
45
45
|
import { type CapturePreviewAction } from './CapturePreview';
|
|
46
46
|
import { type CaptureThumbnailItem } from './CaptureThumbnailStrip';
|
|
47
|
+
import { type DeviceOrientation } from './useDeviceOrientation';
|
|
47
48
|
export type CaptureSource = 'ar' | 'non-ar';
|
|
49
|
+
/**
|
|
50
|
+
* v0.13.2 — which capture sources the host ALLOWS. A constraint on top
|
|
51
|
+
* of `defaultCaptureSource` (which picks the initial source within this
|
|
52
|
+
* constraint):
|
|
53
|
+
* 'both' — AR and non-AR both available; AR toggle is shown.
|
|
54
|
+
* 'ar' — AR only; AR toggle hidden (nothing to switch to), and the
|
|
55
|
+
* 0.5× lens chooser is hidden (ARKit/ARCore don't expose the
|
|
56
|
+
* ultra-wide).
|
|
57
|
+
* 'non-ar' — non-AR only; AR toggle hidden.
|
|
58
|
+
*/
|
|
59
|
+
export type CaptureSourcesMode = 'ar' | 'non-ar' | 'both';
|
|
48
60
|
export type CameraLens = '1x' | '0.5x';
|
|
49
61
|
export type StitchMode = 'auto' | 'panorama' | 'scans';
|
|
50
62
|
export type Blender = 'multiband' | 'feather';
|
|
@@ -142,6 +154,19 @@ export interface CameraProps {
|
|
|
142
154
|
enablePhotoMode?: boolean;
|
|
143
155
|
enablePanoramaMode?: boolean;
|
|
144
156
|
showSettingsButton?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* v0.13.2 — which capture sources the host allows (default `'both'`).
|
|
159
|
+
* Constrains both the runtime AR toggle and `defaultCaptureSource`:
|
|
160
|
+
* - `'both'` : AR + non-AR; the AR toggle is shown so the user can
|
|
161
|
+
* switch at runtime.
|
|
162
|
+
* - `'ar'` : AR only. AR toggle hidden (nothing to toggle); the
|
|
163
|
+
* 0.5× lens chooser is also hidden (ARKit/ARCore can't use the
|
|
164
|
+
* ultra-wide), so the camera stays on the AR-capable 1× lens.
|
|
165
|
+
* - `'non-ar'`: non-AR only. AR toggle hidden.
|
|
166
|
+
* When set to a single source, that source wins regardless of
|
|
167
|
+
* `defaultCaptureSource`.
|
|
168
|
+
*/
|
|
169
|
+
captureSources?: CaptureSourcesMode;
|
|
145
170
|
style?: StyleProp<ViewStyle>;
|
|
146
171
|
/**
|
|
147
172
|
* Which incremental stitcher engine to drive. Default
|
|
@@ -256,22 +281,6 @@ export interface CameraProps {
|
|
|
256
281
|
* `flash` prop) can opt out by setting this to `false`.
|
|
257
282
|
*/
|
|
258
283
|
showFlashButton?: boolean;
|
|
259
|
-
/**
|
|
260
|
-
* v0.13.0 — show the built-in IncrementalPanGuide ("keep the
|
|
261
|
-
* arrow on the line" drift marker) while recording. Defaults
|
|
262
|
-
* to `true`. The guide is gyroscope-driven and only active
|
|
263
|
-
* during the recording phase (no idle sensor cost). Hosts that
|
|
264
|
-
* want their own pan-guide chrome can opt out via `false`.
|
|
265
|
-
*/
|
|
266
|
-
panGuide?: boolean;
|
|
267
|
-
/**
|
|
268
|
-
* v0.13.0 — show the built-in PanoramaGuidance pan-speed pill
|
|
269
|
-
* ("Pan slowly" / "Slow down" / "Too fast") while recording.
|
|
270
|
-
* Defaults to `true`. Gyroscope-driven, only active during
|
|
271
|
-
* recording. Hosts that want their own speed chrome can opt
|
|
272
|
-
* out via `false`.
|
|
273
|
-
*/
|
|
274
|
-
panoramaGuidance?: boolean;
|
|
275
284
|
/**
|
|
276
285
|
* v0.13.0 — built-in CaptureHeader title. When set, `<Camera>`
|
|
277
286
|
* renders a top-of-screen header showing this title (centred)
|
|
@@ -476,4 +485,50 @@ export interface CameraProps {
|
|
|
476
485
|
* The public `<Camera>` component.
|
|
477
486
|
*/
|
|
478
487
|
export declare function Camera(props: CameraProps): React.JSX.Element;
|
|
488
|
+
/**
|
|
489
|
+
* v0.12.0 — JS edge corresponding to the physical home-indicator
|
|
490
|
+
* side of the device. This is where the shutter + controls anchor
|
|
491
|
+
* to so they're always within thumb reach of the user's grip
|
|
492
|
+
* (matching iOS Camera's behaviour).
|
|
493
|
+
*
|
|
494
|
+
* Combines two signals:
|
|
495
|
+
* - `jsLandscape`: whether the OS rotated the framebuffer. True
|
|
496
|
+
* only for non-locked hosts in device-landscape.
|
|
497
|
+
* - `deviceOrient`: physical device orientation from the sensor.
|
|
498
|
+
*
|
|
499
|
+
* Truth table:
|
|
500
|
+
* | jsLandscape | deviceOrient | edge |
|
|
501
|
+
* |--- |--- |--- |
|
|
502
|
+
* | false | any | bottom | (portrait JS coords —
|
|
503
|
+
* | | | | device-bottom = JS-bottom
|
|
504
|
+
* | | | | in both locked and
|
|
505
|
+
* | | | | non-locked-portrait)
|
|
506
|
+
* | true | landscape-left | right | (screen rotated, home
|
|
507
|
+
* | | | | indicator on user-right)
|
|
508
|
+
* | true | landscape-right | left | (mirror)
|
|
509
|
+
*
|
|
510
|
+
* Caveats:
|
|
511
|
+
* - Non-locked + upside-down doesn't surface JS-top here because
|
|
512
|
+
* upside-down doesn't change window dimensions; we can't
|
|
513
|
+
* distinguish locked-portrait-with-device-flipped from
|
|
514
|
+
* non-locked-portrait-with-screen-flipped-180°. Defaults to
|
|
515
|
+
* JS-bottom which matches the more common locked case. Add
|
|
516
|
+
* handling here when a host needs upside-down support.
|
|
517
|
+
* - jsLandscape=true with non-landscape device shouldn't happen
|
|
518
|
+
* in steady state — only during a transition mid-rotation.
|
|
519
|
+
* Falls through to 'right' as a defensive default.
|
|
520
|
+
*/
|
|
521
|
+
type HomeIndicatorEdge = 'bottom' | 'top' | 'left' | 'right';
|
|
522
|
+
declare function homeIndicatorEdge(jsLandscape: boolean, deviceOrient: DeviceOrientation): HomeIndicatorEdge;
|
|
523
|
+
/**
|
|
524
|
+
* v0.12.0 — true when the anchor edge is on a side (left/right), so
|
|
525
|
+
* the band + shutter row need to be vertical strips. Top/bottom
|
|
526
|
+
* anchors yield horizontal strips.
|
|
527
|
+
*/
|
|
528
|
+
declare function isSideEdge(edge: HomeIndicatorEdge): boolean;
|
|
529
|
+
/** @internal test-only — see `homeIndicatorEdge`. */
|
|
530
|
+
export declare const _homeIndicatorEdgeForTests: typeof homeIndicatorEdge;
|
|
531
|
+
/** @internal test-only — see `isSideEdge`. */
|
|
532
|
+
export declare const _isSideEdgeForTests: typeof isSideEdge;
|
|
533
|
+
export {};
|
|
479
534
|
//# sourceMappingURL=Camera.d.ts.map
|