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,327 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* PanoramaGuidance — gyroscope-driven pan-speed indicator for the
|
|
4
|
+
* tap-and-hold panorama flow.
|
|
5
|
+
*
|
|
6
|
+
* ┌──────────────────────────────────────────────────────────┐
|
|
7
|
+
* │ (camera preview) │
|
|
8
|
+
* │ │
|
|
9
|
+
* │ ↓ │ ← portrait + landscape pan
|
|
10
|
+
* │ green / yellow / red │
|
|
11
|
+
* │ │
|
|
12
|
+
* │ "Pan slowly" / "Slow down" / "Too fast" │
|
|
13
|
+
* └──────────────────────────────────────────────────────────┘
|
|
14
|
+
*
|
|
15
|
+
* Why this exists
|
|
16
|
+
* The SCANS-mode stitcher needs ~30–50 % overlap between
|
|
17
|
+
* consecutive frames. At 30 fps, frames are ~33 ms apart, so
|
|
18
|
+
* pan rates above roughly 30°/s (≈ 0.5 rad/s) produce frames
|
|
19
|
+
* the stitcher can't align — and the user finds out only after
|
|
20
|
+
* the post-release "Stitching failed" alert. Real-time feedback
|
|
21
|
+
* prevents that failure mode.
|
|
22
|
+
*
|
|
23
|
+
* What it does
|
|
24
|
+
* - Subscribes to the device gyroscope (react-native-sensors)
|
|
25
|
+
* ONLY while `active` is true; tears down on inactive so the
|
|
26
|
+
* sensor isn't running the rest of the time the screen is up.
|
|
27
|
+
* - Detects portrait vs landscape from window dimensions; the
|
|
28
|
+
* dominant pan axis changes accordingly:
|
|
29
|
+
* portrait → user pans horizontally → we track gyro Y.
|
|
30
|
+
* landscape → user pans vertically → we track gyro X.
|
|
31
|
+
* - Maps the dominant axis's |rad/s| onto a colour scale and a
|
|
32
|
+
* human-readable hint. Defaults are tuned for SCANS but
|
|
33
|
+
* overrideable.
|
|
34
|
+
*
|
|
35
|
+
* Performance
|
|
36
|
+
* The gyroscope fires ~30 Hz. We update an Animated.Value (which
|
|
37
|
+
* updates the colour interpolation on the native driver) and only
|
|
38
|
+
* call setState when the qualitative bucket (good/warn/bad)
|
|
39
|
+
* changes — keeps re-render volume low.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
43
|
+
import {
|
|
44
|
+
StyleSheet,
|
|
45
|
+
Text,
|
|
46
|
+
View,
|
|
47
|
+
type StyleProp,
|
|
48
|
+
type ViewStyle,
|
|
49
|
+
} from 'react-native';
|
|
50
|
+
import {
|
|
51
|
+
gyroscope,
|
|
52
|
+
setUpdateIntervalForType,
|
|
53
|
+
SensorTypes,
|
|
54
|
+
} from 'react-native-sensors';
|
|
55
|
+
import type { Subscription } from 'rxjs';
|
|
56
|
+
|
|
57
|
+
import { useDeviceOrientation } from './useDeviceOrientation';
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export type PanoramaSpeedBucket = 'good' | 'warn' | 'bad';
|
|
61
|
+
|
|
62
|
+
type PanAxis = 'horizontal' | 'vertical';
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
export interface PanoramaGuidanceProps {
|
|
66
|
+
/**
|
|
67
|
+
* Subscribe to the gyroscope only while this is true. Typically
|
|
68
|
+
* driven by the host's `statusPhase === 'recording'`.
|
|
69
|
+
*/
|
|
70
|
+
active: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Force the pan axis instead of auto-detecting from window
|
|
73
|
+
* orientation. Useful for hosts that lock orientation but want
|
|
74
|
+
* the user to pan the orthogonal axis.
|
|
75
|
+
*
|
|
76
|
+
* Default: undefined → auto-detect ("horizontal" in portrait,
|
|
77
|
+
* "vertical" in landscape — matches the user's described
|
|
78
|
+
* "pan top-to-bottom in landscape, left-to-right in portrait").
|
|
79
|
+
*/
|
|
80
|
+
axis?: PanAxis;
|
|
81
|
+
/**
|
|
82
|
+
* Rotation rates in rad/s defining the speed buckets. Defaults
|
|
83
|
+
* tuned for cv::Stitcher::SCANS at 30 fps with iPhone FOV ≈ 70°:
|
|
84
|
+
* |rate| ≤ goodMax → green ("good")
|
|
85
|
+
* |rate| ≤ warnMax → amber ("slow down a bit")
|
|
86
|
+
* else → red ("too fast")
|
|
87
|
+
*/
|
|
88
|
+
goodMaxRadPerSec?: number;
|
|
89
|
+
warnMaxRadPerSec?: number;
|
|
90
|
+
/** Optional hint message overrides. */
|
|
91
|
+
messages?: {
|
|
92
|
+
good?: string;
|
|
93
|
+
warn?: string;
|
|
94
|
+
bad?: string;
|
|
95
|
+
};
|
|
96
|
+
style?: StyleProp<ViewStyle>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
const DEFAULT_GOOD = 0.5;
|
|
101
|
+
const DEFAULT_WARN = 1.0;
|
|
102
|
+
|
|
103
|
+
const COLOR_GOOD = '#34C759';
|
|
104
|
+
const COLOR_WARN = '#FFCC00';
|
|
105
|
+
const COLOR_BAD = '#FF3B30';
|
|
106
|
+
|
|
107
|
+
const DEFAULT_MESSAGES = {
|
|
108
|
+
good: 'Good pace — keep going',
|
|
109
|
+
warn: 'Slow down a bit',
|
|
110
|
+
bad: 'Too fast — slow down',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
function bucketFor(
|
|
115
|
+
rate: number,
|
|
116
|
+
good: number,
|
|
117
|
+
warn: number,
|
|
118
|
+
): PanoramaSpeedBucket {
|
|
119
|
+
const abs = Math.abs(rate);
|
|
120
|
+
if (abs <= good) return 'good';
|
|
121
|
+
if (abs <= warn) return 'warn';
|
|
122
|
+
return 'bad';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
function colorFor(bucket: PanoramaSpeedBucket): string {
|
|
127
|
+
switch (bucket) {
|
|
128
|
+
case 'good':
|
|
129
|
+
return COLOR_GOOD;
|
|
130
|
+
case 'warn':
|
|
131
|
+
return COLOR_WARN;
|
|
132
|
+
case 'bad':
|
|
133
|
+
return COLOR_BAD;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
export function PanoramaGuidance({
|
|
139
|
+
active,
|
|
140
|
+
axis,
|
|
141
|
+
goodMaxRadPerSec = DEFAULT_GOOD,
|
|
142
|
+
warnMaxRadPerSec = DEFAULT_WARN,
|
|
143
|
+
messages,
|
|
144
|
+
style,
|
|
145
|
+
}: PanoramaGuidanceProps): React.JSX.Element | null {
|
|
146
|
+
// Use the accelerometer-based hook (NOT useWindowDimensions) so
|
|
147
|
+
// we detect physical orientation even though the app is
|
|
148
|
+
// portrait-locked at the OS level.
|
|
149
|
+
const deviceOrientation = useDeviceOrientation();
|
|
150
|
+
const isPortrait =
|
|
151
|
+
deviceOrientation === 'portrait'
|
|
152
|
+
|| deviceOrientation === 'portrait-upside-down';
|
|
153
|
+
|
|
154
|
+
// Auto-detect: in portrait the user pans horizontally
|
|
155
|
+
// (left↔right across the rack) → gyro Y axis dominates.
|
|
156
|
+
// In landscape the user pans vertically (up↕down a tall fixture)
|
|
157
|
+
// → gyro X axis dominates.
|
|
158
|
+
const resolvedAxis: PanAxis =
|
|
159
|
+
axis ?? (isPortrait ? 'horizontal' : 'vertical');
|
|
160
|
+
|
|
161
|
+
// Qualitative bucket — drives both the message and (via colour)
|
|
162
|
+
// the arrow tint. Stored in state so a *change* in bucket
|
|
163
|
+
// re-renders, but per-sample updates do NOT.
|
|
164
|
+
const [bucket, setBucket] = useState<PanoramaSpeedBucket>('good');
|
|
165
|
+
|
|
166
|
+
// Last known rotation rate for the dominant axis, kept in a ref
|
|
167
|
+
// to avoid re-rendering every sample. Read by the bucket logic
|
|
168
|
+
// and (indirectly, via colour interpolation) the animated tint.
|
|
169
|
+
const lastBucketRef = useRef<PanoramaSpeedBucket>('good');
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (!active) {
|
|
173
|
+
lastBucketRef.current = 'good';
|
|
174
|
+
setBucket('good');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Sample at 33 ms (~30 Hz) — matches the typical recording
|
|
179
|
+
// frame rate so each gyro sample maps to one frame's pan.
|
|
180
|
+
setUpdateIntervalForType(SensorTypes.gyroscope, 33);
|
|
181
|
+
|
|
182
|
+
let subscription: Subscription | null = gyroscope.subscribe({
|
|
183
|
+
next: ({ x, y }) => {
|
|
184
|
+
const rate = resolvedAxis === 'horizontal' ? y : x;
|
|
185
|
+
const next = bucketFor(rate, goodMaxRadPerSec, warnMaxRadPerSec);
|
|
186
|
+
if (next !== lastBucketRef.current) {
|
|
187
|
+
lastBucketRef.current = next;
|
|
188
|
+
setBucket(next);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
error: (err) => {
|
|
192
|
+
// eslint-disable-next-line no-console
|
|
193
|
+
console.warn('[PanoramaGuidance] gyroscope error', err);
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return () => {
|
|
198
|
+
subscription?.unsubscribe();
|
|
199
|
+
subscription = null;
|
|
200
|
+
};
|
|
201
|
+
}, [active, resolvedAxis, goodMaxRadPerSec, warnMaxRadPerSec]);
|
|
202
|
+
|
|
203
|
+
const resolvedMessages = useMemo(
|
|
204
|
+
() => ({
|
|
205
|
+
good: messages?.good ?? DEFAULT_MESSAGES.good,
|
|
206
|
+
warn: messages?.warn ?? DEFAULT_MESSAGES.warn,
|
|
207
|
+
bad: messages?.bad ?? DEFAULT_MESSAGES.bad,
|
|
208
|
+
}),
|
|
209
|
+
[messages],
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
if (!active) return null;
|
|
213
|
+
|
|
214
|
+
const tint = colorFor(bucket);
|
|
215
|
+
const message = resolvedMessages[bucket];
|
|
216
|
+
// Arrow glyph for the dominant axis. The arrow renders a
|
|
217
|
+
// pannable direction hint — landscape gets a vertical arrow
|
|
218
|
+
// (the user's panning that way), portrait gets a horizontal
|
|
219
|
+
// arrow.
|
|
220
|
+
const arrow = resolvedAxis === 'horizontal' ? '↔' : '↕';
|
|
221
|
+
|
|
222
|
+
// Place the pill at user-perceived bottom across all four
|
|
223
|
+
// orientations. Same pattern as <CaptureStatusOverlay> — the
|
|
224
|
+
// app layout is portrait-locked so we re-position via absolute
|
|
225
|
+
// coords + apply a rotation transform.
|
|
226
|
+
const pillOrientationStyle =
|
|
227
|
+
pillStyleForOrientation(deviceOrientation);
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<View
|
|
231
|
+
pointerEvents="none"
|
|
232
|
+
style={[styles.root, pillOrientationStyle, style]}
|
|
233
|
+
accessibilityRole="alert"
|
|
234
|
+
accessibilityLiveRegion="polite"
|
|
235
|
+
>
|
|
236
|
+
<View style={[styles.pill, { borderColor: tint }]}>
|
|
237
|
+
<Text style={[styles.arrow, { color: tint }]}>{arrow}</Text>
|
|
238
|
+
<Text style={[styles.message, { color: tint }]} numberOfLines={1}>
|
|
239
|
+
{message}
|
|
240
|
+
</Text>
|
|
241
|
+
</View>
|
|
242
|
+
</View>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Mirror of bannerStyleForOrientation in CaptureStatusOverlay,
|
|
249
|
+
* but anchored at user-perceived BOTTOM instead of TOP.
|
|
250
|
+
*/
|
|
251
|
+
function pillStyleForOrientation(
|
|
252
|
+
orientation:
|
|
253
|
+
| 'portrait'
|
|
254
|
+
| 'portrait-upside-down'
|
|
255
|
+
| 'landscape-left'
|
|
256
|
+
| 'landscape-right',
|
|
257
|
+
): {
|
|
258
|
+
top?: number;
|
|
259
|
+
bottom?: number;
|
|
260
|
+
left?: number;
|
|
261
|
+
right?: number;
|
|
262
|
+
alignItems?: 'center' | 'flex-start' | 'flex-end';
|
|
263
|
+
justifyContent?: 'center' | 'flex-start' | 'flex-end';
|
|
264
|
+
transform?: { rotate: string }[];
|
|
265
|
+
} {
|
|
266
|
+
switch (orientation) {
|
|
267
|
+
case 'landscape-left':
|
|
268
|
+
return {
|
|
269
|
+
top: 0,
|
|
270
|
+
bottom: 0,
|
|
271
|
+
left: 8,
|
|
272
|
+
alignItems: 'flex-start',
|
|
273
|
+
justifyContent: 'center',
|
|
274
|
+
transform: [{ rotate: '90deg' }],
|
|
275
|
+
};
|
|
276
|
+
case 'landscape-right':
|
|
277
|
+
return {
|
|
278
|
+
top: 0,
|
|
279
|
+
bottom: 0,
|
|
280
|
+
right: 8,
|
|
281
|
+
alignItems: 'flex-end',
|
|
282
|
+
justifyContent: 'center',
|
|
283
|
+
transform: [{ rotate: '-90deg' }],
|
|
284
|
+
};
|
|
285
|
+
case 'portrait-upside-down':
|
|
286
|
+
return {
|
|
287
|
+
top: 24,
|
|
288
|
+
left: 0,
|
|
289
|
+
right: 0,
|
|
290
|
+
alignItems: 'center',
|
|
291
|
+
transform: [{ rotate: '180deg' }],
|
|
292
|
+
};
|
|
293
|
+
case 'portrait':
|
|
294
|
+
default:
|
|
295
|
+
return {
|
|
296
|
+
bottom: 24,
|
|
297
|
+
left: 0,
|
|
298
|
+
right: 0,
|
|
299
|
+
alignItems: 'center',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
const styles = StyleSheet.create({
|
|
306
|
+
root: {
|
|
307
|
+
position: 'absolute',
|
|
308
|
+
},
|
|
309
|
+
pill: {
|
|
310
|
+
flexDirection: 'row',
|
|
311
|
+
alignItems: 'center',
|
|
312
|
+
paddingHorizontal: 14,
|
|
313
|
+
paddingVertical: 8,
|
|
314
|
+
borderRadius: 24,
|
|
315
|
+
borderWidth: 2,
|
|
316
|
+
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
317
|
+
},
|
|
318
|
+
arrow: {
|
|
319
|
+
fontSize: 22,
|
|
320
|
+
fontWeight: '700',
|
|
321
|
+
marginRight: 8,
|
|
322
|
+
},
|
|
323
|
+
message: {
|
|
324
|
+
fontSize: 14,
|
|
325
|
+
fontWeight: '600',
|
|
326
|
+
},
|
|
327
|
+
});
|