one 2.6.21 → 2.7.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/android/src/main/java/dev/onejs/onenative/OneNativeCameraManager.kt +24 -0
- package/android/src/main/java/dev/onejs/onenative/OneNativeCameraView.kt +197 -2
- package/dist/cjs/platform/camera/index.cjs +3 -2
- package/dist/cjs/platform/camera/index.native.cjs +89 -3
- package/dist/cjs/platform/camera/index.native.js +89 -3
- package/dist/cjs/platform/camera/index.native.js.map +1 -1
- package/dist/cjs/platform/specs/OneNativeCameraNativeComponent.cjs +37 -21
- package/dist/cjs/platform/specs/OneNativeCameraNativeComponent.native.cjs +37 -21
- package/dist/cjs/platform/specs/OneNativeCameraNativeComponent.native.js +37 -21
- package/dist/cjs/platform/specs/OneNativeCameraNativeComponent.native.js.map +3 -3
- package/dist/cjs/router/router.cjs +4 -1
- package/dist/cjs/useHeaderHeight.native.cjs +5 -23
- package/dist/cjs/useHeaderHeight.native.js +5 -23
- package/dist/cjs/useHeaderHeight.native.js.map +1 -1
- package/dist/esm/platform/camera/index.mjs +4 -2
- package/dist/esm/platform/camera/index.mjs.map +1 -1
- package/dist/esm/platform/camera/index.native.js +90 -4
- package/dist/esm/platform/camera/index.native.js.map +1 -1
- package/dist/esm/platform/specs/OneNativeCameraNativeComponent.mjs +25 -9
- package/dist/esm/platform/specs/OneNativeCameraNativeComponent.mjs.map +3 -3
- package/dist/esm/platform/specs/OneNativeCameraNativeComponent.native.js +25 -9
- package/dist/esm/platform/specs/OneNativeCameraNativeComponent.native.js.map +3 -3
- package/dist/esm/router/router.mjs +5 -2
- package/dist/esm/router/router.mjs.map +1 -1
- package/dist/esm/useHeaderHeight.native.js +3 -21
- package/dist/esm/useHeaderHeight.native.js.map +1 -1
- package/ios/Nitro/HybridOneImageManipulator.swift +1 -1
- package/ios/OneNativeCameraComponentView.mm +28 -0
- package/ios/OneNativeCameraView.swift +163 -0
- package/package.json +27 -27
- package/src/index.ts +3 -0
- package/src/platform/camera/index.native.tsx +116 -12
- package/src/platform/camera/index.tsx +15 -4
- package/src/platform/camera/types.ts +22 -0
- package/src/platform/index.native.ts +4 -0
- package/src/platform/index.ts +4 -0
- package/src/platform/specs/OneNativeCameraNativeComponent.ts +35 -2
- package/src/router/router.ts +7 -4
- package/src/useHeaderHeight.native.ts +7 -0
- package/types/index.d.ts +1 -1
- package/types/index.d.ts.map +1 -1
- package/types/platform/camera/index.d.ts +3 -3
- package/types/platform/camera/index.d.ts.map +1 -1
- package/types/platform/camera/index.native.d.ts +3 -3
- package/types/platform/camera/index.native.d.ts.map +1 -1
- package/types/platform/camera/types.d.ts +18 -0
- package/types/platform/camera/types.d.ts.map +1 -1
- package/types/platform/index.d.ts +1 -1
- package/types/platform/index.d.ts.map +1 -1
- package/types/platform/index.native.d.ts +1 -1
- package/types/platform/index.native.d.ts.map +1 -1
- package/types/platform/specs/OneNativeCameraNativeComponent.d.ts +18 -2
- package/types/platform/specs/OneNativeCameraNativeComponent.d.ts.map +1 -1
- package/types/router/router.d.ts.map +1 -1
- package/types/useHeaderHeight.native.d.ts +2 -0
- package/types/useHeaderHeight.native.d.ts.map +1 -0
|
@@ -13,9 +13,28 @@ private final class CameraMetadataDelegate: NSObject, AVCaptureMetadataOutputObj
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// one capture in flight; AVCapturePhotoOutput holds its delegate weakly.
|
|
17
|
+
private final class CameraPhotoDelegate: NSObject, AVCapturePhotoCaptureDelegate {
|
|
18
|
+
let finish: (AVCapturePhoto?, Error?) -> Void
|
|
19
|
+
|
|
20
|
+
init(finish: @escaping (AVCapturePhoto?, Error?) -> Void) {
|
|
21
|
+
self.finish = finish
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func photoOutput(
|
|
25
|
+
_ output: AVCapturePhotoOutput,
|
|
26
|
+
didFinishProcessingPhoto photo: AVCapturePhoto,
|
|
27
|
+
error: Error?
|
|
28
|
+
) {
|
|
29
|
+
finish(photo, error)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
16
33
|
@objcMembers public final class OneNativeCameraView: UIView {
|
|
17
34
|
public var onStateChange: ((String) -> Void)?
|
|
18
35
|
public var onCodeScanned: ((String, String) -> Void)?
|
|
36
|
+
// requestId, uri, width, height, base64, error ("" on success).
|
|
37
|
+
public var onPicture: ((Int, String, Int, Int, String, String) -> Void)?
|
|
19
38
|
|
|
20
39
|
public override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self }
|
|
21
40
|
|
|
@@ -32,6 +51,10 @@ private final class CameraMetadataDelegate: NSObject, AVCaptureMetadataOutputObj
|
|
|
32
51
|
private let sessionQueue = DispatchQueue(label: "dev.one.camera.capture")
|
|
33
52
|
private var session: AVCaptureSession?
|
|
34
53
|
private var metadataOutput: AVCaptureMetadataOutput?
|
|
54
|
+
// owned by sessionQueue.
|
|
55
|
+
private var photoOutput: AVCapturePhotoOutput?
|
|
56
|
+
private var captureDevice: AVCaptureDevice?
|
|
57
|
+
private var photoDelegates: [Int: CameraPhotoDelegate] = [:]
|
|
35
58
|
private let metadataDelegate = CameraMetadataDelegate()
|
|
36
59
|
private var sessionObservers: [NSObjectProtocol] = []
|
|
37
60
|
private var rotationCoordinator: AVCaptureDevice.RotationCoordinator?
|
|
@@ -41,6 +64,9 @@ private final class CameraMetadataDelegate: NSObject, AVCaptureMetadataOutputObj
|
|
|
41
64
|
private var active = false
|
|
42
65
|
private var facing = "back"
|
|
43
66
|
private var codeTypes: [String] = []
|
|
67
|
+
private var flash = "off"
|
|
68
|
+
private var torch = false
|
|
69
|
+
private var zoom: Double = 0
|
|
44
70
|
private var generation = 0
|
|
45
71
|
private var lastState = ""
|
|
46
72
|
private var activeObserver: NSObjectProtocol?
|
|
@@ -82,13 +108,132 @@ private final class CameraMetadataDelegate: NSObject, AVCaptureMetadataOutputObj
|
|
|
82
108
|
refresh(reconfigure: true)
|
|
83
109
|
}
|
|
84
110
|
|
|
111
|
+
// flash, torch and zoom change the running device without restarting the session.
|
|
112
|
+
public func configureCapture(_ flash: String, torch: Bool, zoom: Double) {
|
|
113
|
+
guard self.flash != flash || self.torch != torch || self.zoom != zoom else { return }
|
|
114
|
+
self.flash = flash
|
|
115
|
+
self.torch = torch
|
|
116
|
+
self.zoom = zoom
|
|
117
|
+
sessionQueue.async { self.applyDeviceSettings(torch: torch, zoom: zoom) }
|
|
118
|
+
}
|
|
119
|
+
|
|
85
120
|
public func reset() {
|
|
86
121
|
active = false
|
|
87
122
|
facing = "back"
|
|
88
123
|
codeTypes = []
|
|
124
|
+
flash = "off"
|
|
125
|
+
torch = false
|
|
126
|
+
zoom = 0
|
|
89
127
|
refresh()
|
|
90
128
|
}
|
|
91
129
|
|
|
130
|
+
private func applyDeviceSettings(torch: Bool, zoom: Double) {
|
|
131
|
+
guard let device = captureDevice, (try? device.lockForConfiguration()) != nil else { return }
|
|
132
|
+
defer { device.unlockForConfiguration() }
|
|
133
|
+
let torchMode: AVCaptureDevice.TorchMode = torch ? .on : .off
|
|
134
|
+
if device.hasTorch, device.isTorchModeSupported(torchMode) { device.torchMode = torchMode }
|
|
135
|
+
let low = device.minAvailableVideoZoomFactor
|
|
136
|
+
let high = device.maxAvailableVideoZoomFactor
|
|
137
|
+
device.videoZoomFactor = low + CGFloat(zoom) * (high - low)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public func takePicture(_ requestId: Int, quality: Double, base64: Bool) {
|
|
141
|
+
let fail = { [weak self] (code: String, message: String) in
|
|
142
|
+
self?.onPicture?(requestId, "", 0, 0, "", "\(code): \(message)")
|
|
143
|
+
}
|
|
144
|
+
guard sessionReady, let coordinator = rotationCoordinator else {
|
|
145
|
+
let state = lastState.isEmpty ? "waiting" : lastState
|
|
146
|
+
fail("E_CAMERA_NOT_READY", "CameraView is not ready to take a picture (state: \(state))")
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
let angle = coordinator.videoRotationAngleForHorizonLevelCapture
|
|
150
|
+
let flashName = flash
|
|
151
|
+
let current = generation
|
|
152
|
+
sessionQueue.async { [weak self] in
|
|
153
|
+
guard let self, let output = self.photoOutput,
|
|
154
|
+
output.availablePhotoCodecTypes.contains(.jpeg)
|
|
155
|
+
else {
|
|
156
|
+
DispatchQueue.main.async { fail("E_CAMERA_NOT_READY", "CameraView has no photo output") }
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
let settings = AVCapturePhotoSettings(format: [
|
|
160
|
+
AVVideoCodecKey: AVVideoCodecType.jpeg,
|
|
161
|
+
AVVideoCompressionPropertiesKey: [AVVideoQualityKey: quality],
|
|
162
|
+
])
|
|
163
|
+
let flashMode: AVCaptureDevice.FlashMode =
|
|
164
|
+
flashName == "on" ? .on : flashName == "auto" ? .auto : .off
|
|
165
|
+
if output.supportedFlashModes.contains(flashMode) { settings.flashMode = flashMode }
|
|
166
|
+
if let connection = output.connection(with: .video),
|
|
167
|
+
connection.isVideoRotationAngleSupported(angle)
|
|
168
|
+
{
|
|
169
|
+
connection.videoRotationAngle = angle
|
|
170
|
+
}
|
|
171
|
+
let delegate = CameraPhotoDelegate { [weak self] photo, error in
|
|
172
|
+
guard let self else { return }
|
|
173
|
+
// photo callbacks arrive on the output's private queue.
|
|
174
|
+
self.sessionQueue.async { self.photoDelegates[requestId] = nil }
|
|
175
|
+
let result = Self.savePicture(photo: photo, error: error, base64: base64)
|
|
176
|
+
DispatchQueue.main.async {
|
|
177
|
+
guard self.generation == current else {
|
|
178
|
+
fail("E_CAMERA_INACTIVE", "CameraView stopped before the picture was taken")
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
switch result {
|
|
182
|
+
case .success(let picture):
|
|
183
|
+
self.onPicture?(requestId, picture.uri, picture.width, picture.height, picture.base64, "")
|
|
184
|
+
case .failure(let failure):
|
|
185
|
+
fail("E_CAMERA_CAPTURE", failure.message)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
self.photoDelegates[requestId] = delegate
|
|
190
|
+
output.capturePhoto(with: settings, delegate: delegate)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private struct SavedPicture {
|
|
195
|
+
let uri: String
|
|
196
|
+
let width: Int
|
|
197
|
+
let height: Int
|
|
198
|
+
let base64: String
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private struct CaptureFailure: Error {
|
|
202
|
+
let message: String
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// writes the jpeg to caches/Camera and reports its displayed size: exif
|
|
206
|
+
// orientations 5 to 8 are rotated a quarter turn, so their sides swap.
|
|
207
|
+
private static func savePicture(
|
|
208
|
+
photo: AVCapturePhoto?, error: Error?, base64: Bool
|
|
209
|
+
) -> Result<SavedPicture, CaptureFailure> {
|
|
210
|
+
if let error { return .failure(CaptureFailure(message: error.localizedDescription)) }
|
|
211
|
+
guard let data = photo?.fileDataRepresentation(),
|
|
212
|
+
let source = CGImageSourceCreateWithData(data as CFData, nil),
|
|
213
|
+
let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any],
|
|
214
|
+
let pixelWidth = properties[kCGImagePropertyPixelWidth] as? Int,
|
|
215
|
+
let pixelHeight = properties[kCGImagePropertyPixelHeight] as? Int
|
|
216
|
+
else { return .failure(CaptureFailure(message: "The camera returned no image data")) }
|
|
217
|
+
let orientation = properties[kCGImagePropertyOrientation] as? Int ?? 1
|
|
218
|
+
let quarterTurn = (5...8).contains(orientation)
|
|
219
|
+
do {
|
|
220
|
+
let directory = try FileManager.default
|
|
221
|
+
.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
|
222
|
+
.appendingPathComponent("Camera", isDirectory: true)
|
|
223
|
+
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
|
224
|
+
let file = directory.appendingPathComponent("\(UUID().uuidString).jpg")
|
|
225
|
+
try data.write(to: file, options: .atomic)
|
|
226
|
+
return .success(SavedPicture(
|
|
227
|
+
uri: file.absoluteString,
|
|
228
|
+
width: quarterTurn ? pixelHeight : pixelWidth,
|
|
229
|
+
height: quarterTurn ? pixelWidth : pixelHeight,
|
|
230
|
+
base64: base64 ? data.base64EncodedString() : ""
|
|
231
|
+
))
|
|
232
|
+
} catch {
|
|
233
|
+
return .failure(CaptureFailure(message: error.localizedDescription))
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
92
237
|
private func emitState(_ state: String) {
|
|
93
238
|
guard lastState != state else { return }
|
|
94
239
|
lastState = state
|
|
@@ -115,6 +260,8 @@ private final class CameraMetadataDelegate: NSObject, AVCaptureMetadataOutputObj
|
|
|
115
260
|
sessionQueue.async {
|
|
116
261
|
self.session?.stopRunning()
|
|
117
262
|
self.session = nil
|
|
263
|
+
self.photoOutput = nil
|
|
264
|
+
self.captureDevice = nil
|
|
118
265
|
}
|
|
119
266
|
emitState("inactive")
|
|
120
267
|
return
|
|
@@ -125,6 +272,8 @@ private final class CameraMetadataDelegate: NSObject, AVCaptureMetadataOutputObj
|
|
|
125
272
|
sessionQueue.async {
|
|
126
273
|
self.session?.stopRunning()
|
|
127
274
|
self.session = nil
|
|
275
|
+
self.photoOutput = nil
|
|
276
|
+
self.captureDevice = nil
|
|
128
277
|
}
|
|
129
278
|
emitState("permission-required")
|
|
130
279
|
return
|
|
@@ -142,6 +291,8 @@ private final class CameraMetadataDelegate: NSObject, AVCaptureMetadataOutputObj
|
|
|
142
291
|
guard let self else { return }
|
|
143
292
|
self.session?.stopRunning()
|
|
144
293
|
self.session = nil
|
|
294
|
+
self.photoOutput = nil
|
|
295
|
+
self.captureDevice = nil
|
|
145
296
|
guard let device = AVCaptureDevice.default(
|
|
146
297
|
.builtInWideAngleCamera, for: .video, position: position
|
|
147
298
|
) else {
|
|
@@ -178,7 +329,16 @@ private final class CameraMetadataDelegate: NSObject, AVCaptureMetadataOutputObj
|
|
|
178
329
|
metadata.setMetadataObjectsDelegate(self.metadataDelegate, queue: self.sessionQueue)
|
|
179
330
|
output = metadata
|
|
180
331
|
}
|
|
332
|
+
let photo = AVCapturePhotoOutput()
|
|
333
|
+
guard next.canAddOutput(photo) else {
|
|
334
|
+
next.commitConfiguration()
|
|
335
|
+
self.emitState("failed", generation: current)
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
next.addOutput(photo)
|
|
181
339
|
next.commitConfiguration()
|
|
340
|
+
self.photoOutput = photo
|
|
341
|
+
self.captureDevice = device
|
|
182
342
|
|
|
183
343
|
var readyState = "ready"
|
|
184
344
|
if let output {
|
|
@@ -211,8 +371,11 @@ private final class CameraMetadataDelegate: NSObject, AVCaptureMetadataOutputObj
|
|
|
211
371
|
}
|
|
212
372
|
self.observeSession(next)
|
|
213
373
|
let queue = self.sessionQueue
|
|
374
|
+
let torch = self.torch
|
|
375
|
+
let zoom = self.zoom
|
|
214
376
|
queue.async { [weak self] in
|
|
215
377
|
next.startRunning()
|
|
378
|
+
self?.applyDeviceSettings(torch: torch, zoom: zoom)
|
|
216
379
|
let running = next.isRunning
|
|
217
380
|
DispatchQueue.main.async { [weak self] in
|
|
218
381
|
guard let self, self.generation == current else { return }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "one",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "One is a React framework for web, iOS, and Android apps.",
|
|
5
5
|
"license": "BSD-3-Clause",
|
|
6
6
|
"sideEffects": [
|
|
@@ -231,23 +231,23 @@
|
|
|
231
231
|
"@react-native-community/cli-platform-android": "^20.2.0",
|
|
232
232
|
"@react-native-community/cli-platform-ios": "^20.2.0",
|
|
233
233
|
"@radix-ui/react-slot": "^1.0.2",
|
|
234
|
-
"@react-navigation/bottom-tabs": "8.0.0-alpha.
|
|
235
|
-
"@react-navigation/elements": "3.0.0-alpha.
|
|
236
|
-
"@react-navigation/native-stack": "8.0.0-alpha.
|
|
237
|
-
"@react-navigation/routers": "8.0.0-alpha.
|
|
234
|
+
"@react-navigation/bottom-tabs": "8.0.0-alpha.57",
|
|
235
|
+
"@react-navigation/elements": "3.0.0-alpha.55",
|
|
236
|
+
"@react-navigation/native-stack": "8.0.0-alpha.59",
|
|
237
|
+
"@react-navigation/routers": "8.0.0-alpha.21",
|
|
238
238
|
"@ungap/structured-clone": "^1.2.0",
|
|
239
|
-
"@vxrn/color-scheme": "2.
|
|
240
|
-
"@vxrn/compiler": "2.
|
|
241
|
-
"@vxrn/resolve": "2.
|
|
242
|
-
"@vxrn/tslib-lite": "2.
|
|
243
|
-
"@vxrn/use-isomorphic-layout-effect": "2.
|
|
244
|
-
"@vxrn/utils": "2.
|
|
245
|
-
"@vxrn/vite-plugin-metro": "2.
|
|
239
|
+
"@vxrn/color-scheme": "2.7.0",
|
|
240
|
+
"@vxrn/compiler": "2.7.0",
|
|
241
|
+
"@vxrn/resolve": "2.7.0",
|
|
242
|
+
"@vxrn/tslib-lite": "2.7.0",
|
|
243
|
+
"@vxrn/use-isomorphic-layout-effect": "2.7.0",
|
|
244
|
+
"@vxrn/utils": "2.7.0",
|
|
245
|
+
"@vxrn/vite-plugin-metro": "2.7.0",
|
|
246
246
|
"babel-dead-code-elimination": "1.0.10",
|
|
247
247
|
"babel-plugin-module-resolver": "^5.0.2",
|
|
248
248
|
"citty": "^0.1.6",
|
|
249
249
|
"core-js": "^3.38.1",
|
|
250
|
-
"create-vxrn": "2.
|
|
250
|
+
"create-vxrn": "2.7.0",
|
|
251
251
|
"escape-string-regexp": "^5.0.0",
|
|
252
252
|
"fast-deep-equal": "^3.1.3",
|
|
253
253
|
"fast-glob": "^3.3.3",
|
|
@@ -269,7 +269,7 @@
|
|
|
269
269
|
"tsconfig-paths": "^4",
|
|
270
270
|
"use-latest-callback": "^0.2.3",
|
|
271
271
|
"vite": "^8.2.2",
|
|
272
|
-
"vxrn": "2.
|
|
272
|
+
"vxrn": "2.7.0",
|
|
273
273
|
"web-streams-polyfill": "^4.3.0",
|
|
274
274
|
"ws": "^8.21.0",
|
|
275
275
|
"xxhashjs": "^0.2.2"
|
|
@@ -277,15 +277,15 @@
|
|
|
277
277
|
"peerDependencies": {
|
|
278
278
|
"@callstack/liquid-glass": ">=0.6.0",
|
|
279
279
|
"@react-native/babel-preset": "^0.87.1",
|
|
280
|
-
"@react-navigation/core": "8.0.0-alpha.
|
|
281
|
-
"@react-navigation/drawer": "8.0.0-alpha.
|
|
282
|
-
"@react-navigation/native": "8.0.0-alpha.
|
|
280
|
+
"@react-navigation/core": "8.0.0-alpha.41",
|
|
281
|
+
"@react-navigation/drawer": "8.0.0-alpha.58",
|
|
282
|
+
"@react-navigation/native": "8.0.0-alpha.51",
|
|
283
283
|
"react": "^19.2.3",
|
|
284
284
|
"react-dom": "^19.2.3",
|
|
285
285
|
"react-native": "^0.87.0",
|
|
286
286
|
"react-native-gesture-handler": ">=3.0.0",
|
|
287
287
|
"react-native-reanimated": ">=4.0.0",
|
|
288
|
-
"react-native-screens": ">=4.
|
|
288
|
+
"react-native-screens": ">=4.30.0",
|
|
289
289
|
"react-native-web": "^0.21.2",
|
|
290
290
|
"react-native-worklets": ">=0.12.2",
|
|
291
291
|
"sharp": "^0.35.5",
|
|
@@ -325,19 +325,19 @@
|
|
|
325
325
|
}
|
|
326
326
|
},
|
|
327
327
|
"devDependencies": {
|
|
328
|
-
"@tamagui/sheet": "3.0.
|
|
328
|
+
"@tamagui/sheet": "3.0.10",
|
|
329
329
|
"@callstack/liquid-glass": "^0.8.1",
|
|
330
330
|
"@react-native/babel-plugin-codegen": "0.87.1",
|
|
331
331
|
"@react-native/codegen": "^0.87.1",
|
|
332
|
-
"@react-navigation/core": "8.0.0-alpha.
|
|
333
|
-
"@react-navigation/drawer": "8.0.0-alpha.
|
|
334
|
-
"@react-navigation/native": "8.0.0-alpha.
|
|
335
|
-
"@tamagui/build": "3.0.
|
|
332
|
+
"@react-navigation/core": "8.0.0-alpha.41",
|
|
333
|
+
"@react-navigation/drawer": "8.0.0-alpha.58",
|
|
334
|
+
"@react-navigation/native": "8.0.0-alpha.51",
|
|
335
|
+
"@tamagui/build": "3.0.10",
|
|
336
336
|
"@types/node": "^24.10.0",
|
|
337
337
|
"@types/react-dom": "^19.2.2",
|
|
338
338
|
"@types/react-test-renderer": "^19.1.0",
|
|
339
339
|
"@types/xxhashjs": "^0.2.4",
|
|
340
|
-
"@vxrn/emitter": "2.
|
|
340
|
+
"@vxrn/emitter": "2.7.0",
|
|
341
341
|
"depcheck": "^1.4.7",
|
|
342
342
|
"esbuild": "^0.25.12",
|
|
343
343
|
"immer": "^10.1.1",
|
|
@@ -347,7 +347,7 @@
|
|
|
347
347
|
"react-native": "0.87.1",
|
|
348
348
|
"react-native-gesture-handler": "~3.3.0",
|
|
349
349
|
"react-native-reanimated": "~4.6.0",
|
|
350
|
-
"react-native-screens": "~4.
|
|
350
|
+
"react-native-screens": "~4.30.0",
|
|
351
351
|
"react-native-web": "^0.21.2",
|
|
352
352
|
"react-native-worklets": "~0.12.2",
|
|
353
353
|
"react-test-renderer": "^19.2.0",
|
|
@@ -356,7 +356,7 @@
|
|
|
356
356
|
"typescript": "7.0.2",
|
|
357
357
|
"vitest": "^4.1.0",
|
|
358
358
|
"yoga-layout": "^3.2.1",
|
|
359
|
-
"@tamagui/web": "3.0.
|
|
359
|
+
"@tamagui/web": "3.0.10"
|
|
360
360
|
},
|
|
361
361
|
"publishConfig": {
|
|
362
362
|
"access": "public"
|
|
@@ -513,7 +513,7 @@
|
|
|
513
513
|
}
|
|
514
514
|
}
|
|
515
515
|
},
|
|
516
|
-
"releaseSourceCommit": "
|
|
516
|
+
"releaseSourceCommit": "c3cd8830a7de8c60de78420b946a6ee56ef01ea0",
|
|
517
517
|
"repository": {
|
|
518
518
|
"type": "git",
|
|
519
519
|
"url": "git+https://github.com/onejs/one.git",
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,43 @@
|
|
|
1
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
|
1
2
|
import { Platform } from 'react-native'
|
|
2
|
-
import NativeCamera from '../specs/OneNativeCameraNativeComponent'
|
|
3
|
-
import type {
|
|
3
|
+
import NativeCamera, { Commands } from '../specs/OneNativeCameraNativeComponent'
|
|
4
|
+
import type {
|
|
5
|
+
CameraCapturedPicture,
|
|
6
|
+
CameraCodeType,
|
|
7
|
+
CameraState,
|
|
8
|
+
CameraViewProps,
|
|
9
|
+
CameraViewRef,
|
|
10
|
+
} from './types'
|
|
4
11
|
|
|
5
|
-
export type {
|
|
12
|
+
export type {
|
|
13
|
+
CameraCapturedPicture,
|
|
14
|
+
CameraCode,
|
|
15
|
+
CameraCodeType,
|
|
16
|
+
CameraFacing,
|
|
17
|
+
CameraFlashMode,
|
|
18
|
+
CameraPictureOptions,
|
|
19
|
+
CameraState,
|
|
20
|
+
CameraViewProps,
|
|
21
|
+
CameraViewRef,
|
|
22
|
+
} from './types'
|
|
23
|
+
|
|
24
|
+
type PendingPicture = {
|
|
25
|
+
resolve: (picture: CameraCapturedPicture) => void
|
|
26
|
+
reject: (error: Error) => void
|
|
27
|
+
base64: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let nextRequestId = 1
|
|
31
|
+
|
|
32
|
+
function codedError(code: string, message: string) {
|
|
33
|
+
return Object.assign(new Error(message), { code })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// native failures arrive as "<code>: <message>", the same form nitro rejects with.
|
|
37
|
+
function nativeError(value: string) {
|
|
38
|
+
const coded = /^((?:E|ERR)_[A-Z0-9_]+): ([\s\S]*)$/.exec(value)
|
|
39
|
+
return coded ? codedError(coded[1], coded[2]) : new Error(value)
|
|
40
|
+
}
|
|
6
41
|
|
|
7
42
|
function cameraState(value: string): CameraState {
|
|
8
43
|
switch (value) {
|
|
@@ -41,14 +76,20 @@ function codeType(value: string): CameraCodeType {
|
|
|
41
76
|
return value
|
|
42
77
|
}
|
|
43
78
|
|
|
44
|
-
export function CameraView(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
79
|
+
export const CameraView = forwardRef<CameraViewRef, CameraViewProps>(function CameraView(
|
|
80
|
+
{
|
|
81
|
+
active = true,
|
|
82
|
+
facing = 'back',
|
|
83
|
+
codeTypes = ['qr'],
|
|
84
|
+
flash = 'off',
|
|
85
|
+
enableTorch = false,
|
|
86
|
+
zoom = 0,
|
|
87
|
+
onStateChange,
|
|
88
|
+
onCodeScanned,
|
|
89
|
+
...viewProps
|
|
90
|
+
},
|
|
91
|
+
ref
|
|
92
|
+
) {
|
|
52
93
|
if (Platform.OS !== 'ios' && Platform.OS !== 'android')
|
|
53
94
|
throw new Error('CameraView requires a native build')
|
|
54
95
|
if (typeof active !== 'boolean') throw new TypeError('CameraView.active must be a boolean')
|
|
@@ -58,16 +99,79 @@ export function CameraView({
|
|
|
58
99
|
if (!Array.isArray(codeTypes) || codeTypes.some((type) => !isCodeType(type))) {
|
|
59
100
|
throw new TypeError('CameraView.codeTypes contains an unsupported code type')
|
|
60
101
|
}
|
|
102
|
+
if (flash !== 'off' && flash !== 'on' && flash !== 'auto') {
|
|
103
|
+
throw new TypeError('CameraView.flash must be off, on or auto')
|
|
104
|
+
}
|
|
105
|
+
if (typeof enableTorch !== 'boolean') {
|
|
106
|
+
throw new TypeError('CameraView.enableTorch must be a boolean')
|
|
107
|
+
}
|
|
108
|
+
if (typeof zoom !== 'number' || !(zoom >= 0 && zoom <= 1)) {
|
|
109
|
+
throw new RangeError('CameraView.zoom must be a number from 0 to 1')
|
|
110
|
+
}
|
|
111
|
+
const nativeRef = useRef<React.ElementRef<typeof NativeCamera>>(null)
|
|
112
|
+
const pending = useRef(new Map<number, PendingPicture>())
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
const requests = pending.current
|
|
116
|
+
return () => {
|
|
117
|
+
for (const request of requests.values())
|
|
118
|
+
request.reject(codedError('E_CAMERA_UNMOUNTED', 'CameraView unmounted before the picture was taken'))
|
|
119
|
+
requests.clear()
|
|
120
|
+
}
|
|
121
|
+
}, [])
|
|
122
|
+
|
|
123
|
+
useImperativeHandle(
|
|
124
|
+
ref,
|
|
125
|
+
() => ({
|
|
126
|
+
takePicture(options = {}) {
|
|
127
|
+
const { quality = 1, base64 = false } = options
|
|
128
|
+
if (typeof quality !== 'number' || !(quality >= 0 && quality <= 1))
|
|
129
|
+
return Promise.reject(new RangeError('takePicture quality must be a number from 0 to 1'))
|
|
130
|
+
if (typeof base64 !== 'boolean')
|
|
131
|
+
return Promise.reject(new TypeError('takePicture base64 must be a boolean'))
|
|
132
|
+
const view = nativeRef.current
|
|
133
|
+
if (!view)
|
|
134
|
+
return Promise.reject(codedError('E_CAMERA_UNMOUNTED', 'CameraView is not mounted'))
|
|
135
|
+
const requestId = nextRequestId++
|
|
136
|
+
return new Promise<CameraCapturedPicture>((resolve, reject) => {
|
|
137
|
+
pending.current.set(requestId, { resolve, reject, base64 })
|
|
138
|
+
Commands.takePicture(view, requestId, quality, base64)
|
|
139
|
+
})
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
[]
|
|
143
|
+
)
|
|
144
|
+
|
|
61
145
|
return (
|
|
62
146
|
<NativeCamera
|
|
63
147
|
{...viewProps}
|
|
148
|
+
ref={nativeRef}
|
|
64
149
|
active={active}
|
|
65
150
|
facing={facing}
|
|
66
151
|
codeTypes={[...new Set(codeTypes)]}
|
|
152
|
+
flash={flash}
|
|
153
|
+
enableTorch={enableTorch}
|
|
154
|
+
zoom={zoom}
|
|
67
155
|
onNativeCameraState={({ nativeEvent }) => onStateChange?.(cameraState(nativeEvent.state))}
|
|
68
156
|
onNativeCameraCode={({ nativeEvent }) =>
|
|
69
157
|
onCodeScanned?.({ type: codeType(nativeEvent.type), data: nativeEvent.data })
|
|
70
158
|
}
|
|
159
|
+
onNativeCameraPicture={({ nativeEvent }) => {
|
|
160
|
+
const request = pending.current.get(nativeEvent.requestId)
|
|
161
|
+
if (!request) return
|
|
162
|
+
pending.current.delete(nativeEvent.requestId)
|
|
163
|
+
if (nativeEvent.error) {
|
|
164
|
+
request.reject(nativeError(nativeEvent.error))
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
request.resolve({
|
|
168
|
+
uri: nativeEvent.uri,
|
|
169
|
+
width: nativeEvent.width,
|
|
170
|
+
height: nativeEvent.height,
|
|
171
|
+
format: 'jpg',
|
|
172
|
+
...(request.base64 ? { base64: nativeEvent.base64 } : {}),
|
|
173
|
+
})
|
|
174
|
+
}}
|
|
71
175
|
/>
|
|
72
176
|
)
|
|
73
|
-
}
|
|
177
|
+
})
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { forwardRef } from 'react'
|
|
2
|
+
import type { CameraViewProps, CameraViewRef } from './types'
|
|
2
3
|
|
|
3
|
-
export type {
|
|
4
|
+
export type {
|
|
5
|
+
CameraCapturedPicture,
|
|
6
|
+
CameraCode,
|
|
7
|
+
CameraCodeType,
|
|
8
|
+
CameraFacing,
|
|
9
|
+
CameraFlashMode,
|
|
10
|
+
CameraPictureOptions,
|
|
11
|
+
CameraState,
|
|
12
|
+
CameraViewProps,
|
|
13
|
+
CameraViewRef,
|
|
14
|
+
} from './types'
|
|
4
15
|
|
|
5
|
-
export function CameraView(
|
|
16
|
+
export const CameraView = forwardRef<CameraViewRef, CameraViewProps>(function CameraView(): never {
|
|
6
17
|
throw new Error('CameraView requires a native build')
|
|
7
|
-
}
|
|
18
|
+
})
|
|
@@ -27,10 +27,32 @@ export interface CameraCode {
|
|
|
27
27
|
data: string
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export type CameraFlashMode = 'off' | 'on' | 'auto'
|
|
31
|
+
|
|
30
32
|
export interface CameraViewProps extends ViewProps {
|
|
31
33
|
active?: boolean
|
|
32
34
|
facing?: CameraFacing
|
|
33
35
|
codeTypes?: readonly CameraCodeType[]
|
|
36
|
+
flash?: CameraFlashMode
|
|
37
|
+
enableTorch?: boolean
|
|
38
|
+
zoom?: number
|
|
34
39
|
onStateChange?: (state: CameraState) => void
|
|
35
40
|
onCodeScanned?: (code: CameraCode) => void
|
|
36
41
|
}
|
|
42
|
+
|
|
43
|
+
export interface CameraPictureOptions {
|
|
44
|
+
quality?: number
|
|
45
|
+
base64?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface CameraCapturedPicture {
|
|
49
|
+
uri: string
|
|
50
|
+
width: number
|
|
51
|
+
height: number
|
|
52
|
+
format: 'jpg'
|
|
53
|
+
base64?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface CameraViewRef {
|
|
57
|
+
takePicture(options?: CameraPictureOptions): Promise<CameraCapturedPicture>
|
|
58
|
+
}
|
|
@@ -214,11 +214,15 @@ export type {
|
|
|
214
214
|
export { Audio } from './audio/index'
|
|
215
215
|
export { CameraView } from './camera/index.native'
|
|
216
216
|
export type {
|
|
217
|
+
CameraCapturedPicture,
|
|
217
218
|
CameraCode,
|
|
218
219
|
CameraCodeType,
|
|
219
220
|
CameraFacing,
|
|
221
|
+
CameraFlashMode,
|
|
222
|
+
CameraPictureOptions,
|
|
220
223
|
CameraState,
|
|
221
224
|
CameraViewProps,
|
|
225
|
+
CameraViewRef,
|
|
222
226
|
} from './camera/index.native'
|
|
223
227
|
export type {
|
|
224
228
|
AudioPlaybackState,
|
package/src/platform/index.ts
CHANGED
|
@@ -88,11 +88,15 @@ export type {
|
|
|
88
88
|
export { Audio } from './audio/index'
|
|
89
89
|
export { CameraView } from './camera/index'
|
|
90
90
|
export type {
|
|
91
|
+
CameraCapturedPicture,
|
|
91
92
|
CameraCode,
|
|
92
93
|
CameraCodeType,
|
|
93
94
|
CameraFacing,
|
|
95
|
+
CameraFlashMode,
|
|
96
|
+
CameraPictureOptions,
|
|
94
97
|
CameraState,
|
|
95
98
|
CameraViewProps,
|
|
99
|
+
CameraViewRef,
|
|
96
100
|
} from './camera/index'
|
|
97
101
|
export type {
|
|
98
102
|
AudioPlaybackState,
|
|
@@ -1,13 +1,46 @@
|
|
|
1
|
-
import type { ViewProps } from 'react-native'
|
|
2
|
-
import type {
|
|
1
|
+
import type { HostComponent, ViewProps } from 'react-native'
|
|
2
|
+
import type {
|
|
3
|
+
DirectEventHandler,
|
|
4
|
+
Double,
|
|
5
|
+
Int32,
|
|
6
|
+
} from 'react-native/Libraries/Types/CodegenTypes'
|
|
7
|
+
import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands'
|
|
3
8
|
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'
|
|
9
|
+
import type * as React from 'react'
|
|
4
10
|
|
|
5
11
|
interface NativeProps extends ViewProps {
|
|
6
12
|
active: boolean
|
|
7
13
|
facing: string
|
|
8
14
|
codeTypes: ReadonlyArray<string>
|
|
15
|
+
flash: string
|
|
16
|
+
enableTorch: boolean
|
|
17
|
+
zoom: Double
|
|
9
18
|
onNativeCameraState?: DirectEventHandler<Readonly<{ state: string }>>
|
|
10
19
|
onNativeCameraCode?: DirectEventHandler<Readonly<{ type: string; data: string }>>
|
|
20
|
+
// error is empty on success, otherwise "<code>: <message>".
|
|
21
|
+
onNativeCameraPicture?: DirectEventHandler<
|
|
22
|
+
Readonly<{
|
|
23
|
+
requestId: Int32
|
|
24
|
+
uri: string
|
|
25
|
+
width: Int32
|
|
26
|
+
height: Int32
|
|
27
|
+
base64: string
|
|
28
|
+
error: string
|
|
29
|
+
}>
|
|
30
|
+
>
|
|
11
31
|
}
|
|
12
32
|
|
|
33
|
+
interface NativeCommands {
|
|
34
|
+
takePicture(
|
|
35
|
+
viewRef: React.ElementRef<HostComponent<NativeProps>>,
|
|
36
|
+
requestId: Int32,
|
|
37
|
+
quality: Double,
|
|
38
|
+
base64: boolean
|
|
39
|
+
): void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const Commands = codegenNativeCommands<NativeCommands>({
|
|
43
|
+
supportedCommands: ['takePicture'],
|
|
44
|
+
})
|
|
45
|
+
|
|
13
46
|
export default codegenNativeComponent<NativeProps>('OneNativeCamera')
|