react-native-picture-selector 1.0.27 → 1.0.28
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/ios/HybridPictureSelector.swift +98 -134
- package/package.json +1 -1
|
@@ -15,27 +15,14 @@ import NitroModules
|
|
|
15
15
|
// - Nitro calls openPicker / openCamera on the JS thread.
|
|
16
16
|
// - All UIKit calls are dispatched to DispatchQueue.main.
|
|
17
17
|
// - Async result mapping runs in a Swift Task (cooperative thread pool).
|
|
18
|
-
//
|
|
19
|
-
// API REQUIRES VERIFICATION:
|
|
20
|
-
// - PhotoPickerControllerDelegate method signatures in HXPhotoPicker v5.0.5.
|
|
21
|
-
// - PhotoAsset.getURL(compression:result:) callback API availability.
|
|
22
|
-
// - PickerResult.photoAssets field name.
|
|
23
|
-
// - PhotoAsset.mediaType enum values (.photo / .video).
|
|
24
|
-
// - PhotoAsset.imageSize property name.
|
|
25
|
-
// - PhotoAsset.videoDuration unit (seconds vs ms).
|
|
26
|
-
// - AssetURLResult.fileSize field name / optionality.
|
|
27
|
-
// - PhotoAsset.Compression type name and initialiser parameters.
|
|
28
|
-
// - EditorConfiguration.Photo.CropSize.isRoundCrop property name.
|
|
29
18
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
19
|
|
|
31
20
|
final class HybridPictureSelector: HybridHybridPictureSelectorSpec_base, HybridHybridPictureSelectorSpec_protocol {
|
|
32
21
|
|
|
33
22
|
// MARK: - Private state
|
|
34
23
|
|
|
35
|
-
/// Bundles the pending resolver together with the options so the delegate
|
|
36
|
-
/// can perform compression-aware result mapping.
|
|
37
24
|
private struct PendingSession {
|
|
38
|
-
let
|
|
25
|
+
let promise: Promise<[MediaAsset]>
|
|
39
26
|
let options: PictureSelectorOptions
|
|
40
27
|
}
|
|
41
28
|
|
|
@@ -47,110 +34,104 @@ final class HybridPictureSelector: HybridHybridPictureSelectorSpec_base, HybridH
|
|
|
47
34
|
// MARK: - openPicker
|
|
48
35
|
|
|
49
36
|
func openPicker(options: PictureSelectorOptions) -> Promise<[MediaAsset]> {
|
|
50
|
-
|
|
37
|
+
let promise = Promise<[MediaAsset]>()
|
|
38
|
+
|
|
39
|
+
DispatchQueue.main.async { [weak self] in
|
|
51
40
|
guard let self else {
|
|
52
|
-
|
|
41
|
+
promise.reject(withError: PictureSelectorError.unknown("openPicker: native module was deallocated"))
|
|
53
42
|
return
|
|
54
43
|
}
|
|
55
44
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
guard let topVC = self.topViewController() else {
|
|
65
|
-
resolver.reject(PictureSelectorError.unknown(
|
|
66
|
-
"No active UIViewController. Ensure the picker is called from a mounted component."
|
|
67
|
-
))
|
|
68
|
-
return
|
|
69
|
-
}
|
|
45
|
+
if self.session != nil {
|
|
46
|
+
promise.reject(withError: PictureSelectorError.unknown(
|
|
47
|
+
"A picker or camera session is already active. Dismiss it before opening a new one."
|
|
48
|
+
))
|
|
49
|
+
return
|
|
50
|
+
}
|
|
70
51
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
options: options
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
let config = self.buildPickerConfig(from: options)
|
|
82
|
-
let picker = PhotoPickerController(picker: config)
|
|
83
|
-
picker.pickerDelegate = self
|
|
84
|
-
|
|
85
|
-
self.activePicker = picker
|
|
86
|
-
topVC.present(picker, animated: true)
|
|
52
|
+
guard let topVC = self.topViewController() else {
|
|
53
|
+
promise.reject(withError: PictureSelectorError.unknown(
|
|
54
|
+
"No active UIViewController. Ensure the picker is called from a mounted component."
|
|
55
|
+
))
|
|
56
|
+
return
|
|
87
57
|
}
|
|
58
|
+
|
|
59
|
+
self.session = PendingSession(promise: promise, options: options)
|
|
60
|
+
|
|
61
|
+
let config = self.buildPickerConfig(from: options)
|
|
62
|
+
let picker = PhotoPickerController(picker: config)
|
|
63
|
+
picker.pickerDelegate = self
|
|
64
|
+
|
|
65
|
+
self.activePicker = picker
|
|
66
|
+
topVC.present(picker, animated: true)
|
|
88
67
|
}
|
|
68
|
+
|
|
69
|
+
return promise
|
|
89
70
|
}
|
|
90
71
|
|
|
91
72
|
// MARK: - openCamera
|
|
92
73
|
|
|
93
74
|
func openCamera(options: PictureSelectorOptions) -> Promise<[MediaAsset]> {
|
|
94
|
-
|
|
75
|
+
let promise = Promise<[MediaAsset]>()
|
|
76
|
+
|
|
77
|
+
DispatchQueue.main.async { [weak self] in
|
|
95
78
|
guard let self else {
|
|
96
|
-
|
|
79
|
+
promise.reject(withError: PictureSelectorError.unknown("openCamera: native module was deallocated"))
|
|
97
80
|
return
|
|
98
81
|
}
|
|
99
82
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
83
|
+
if self.session != nil {
|
|
84
|
+
promise.reject(withError: PictureSelectorError.unknown(
|
|
85
|
+
"A picker or camera session is already active. Dismiss it before opening a new one."
|
|
86
|
+
))
|
|
87
|
+
return
|
|
88
|
+
}
|
|
107
89
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
90
|
+
guard let topVC = self.topViewController() else {
|
|
91
|
+
promise.reject(withError: PictureSelectorError.unknown("No active UIViewController."))
|
|
92
|
+
return
|
|
93
|
+
}
|
|
112
94
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
95
|
+
var cameraConfig = CameraConfiguration()
|
|
96
|
+
if let maxDur = options.maxVideoDuration {
|
|
97
|
+
cameraConfig.videoMaximumDuration = maxDur
|
|
98
|
+
}
|
|
118
99
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
isOriginal: false
|
|
143
|
-
)
|
|
144
|
-
resolver.resolve([asset])
|
|
145
|
-
} catch {
|
|
146
|
-
resolver.reject(error)
|
|
147
|
-
}
|
|
100
|
+
let captureType: CameraController.CaptureType
|
|
101
|
+
switch options.mediaType {
|
|
102
|
+
case .video: captureType = .video
|
|
103
|
+
case .all: captureType = .all
|
|
104
|
+
default: captureType = .photo
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let camera = CameraController(config: cameraConfig, type: captureType)
|
|
108
|
+
camera.completion = { [weak self] result, _, _ in
|
|
109
|
+
guard let self else {
|
|
110
|
+
promise.reject(withError: PictureSelectorError.unknown("openCamera: native module was deallocated"))
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
Task {
|
|
114
|
+
do {
|
|
115
|
+
let asset = try await self.mapAsset(
|
|
116
|
+
result.photoAsset,
|
|
117
|
+
compress: options.compress,
|
|
118
|
+
isOriginal: false
|
|
119
|
+
)
|
|
120
|
+
promise.resolve(withResult: [asset])
|
|
121
|
+
} catch {
|
|
122
|
+
promise.reject(withError: error)
|
|
148
123
|
}
|
|
149
|
-
} cancel: { _ in
|
|
150
|
-
resolver.reject(PictureSelectorError.cancelled)
|
|
151
124
|
}
|
|
152
125
|
}
|
|
126
|
+
camera.cancelHandler = { _ in
|
|
127
|
+
promise.reject(withError: PictureSelectorError.cancelled)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
self.activePicker = camera
|
|
131
|
+
topVC.present(camera, animated: true)
|
|
153
132
|
}
|
|
133
|
+
|
|
134
|
+
return promise
|
|
154
135
|
}
|
|
155
136
|
|
|
156
137
|
// MARK: - Config builder
|
|
@@ -172,36 +153,32 @@ final class HybridPictureSelector: HybridHybridPictureSelectorSpec_base, HybridH
|
|
|
172
153
|
config.maximumSelectedCount = Int(options.maxCount ?? 1)
|
|
173
154
|
|
|
174
155
|
// In-picker camera button
|
|
175
|
-
config.
|
|
156
|
+
config.photoList.allowAddCamera = options.enableCamera ?? true
|
|
176
157
|
|
|
177
|
-
// Video duration limits
|
|
158
|
+
// Video duration limits (Int in HXPhotoPicker)
|
|
178
159
|
if let maxDur = options.maxVideoDuration {
|
|
179
|
-
config.maximumSelectedVideoDuration = maxDur
|
|
160
|
+
config.maximumSelectedVideoDuration = Int(maxDur)
|
|
180
161
|
}
|
|
181
162
|
if let minDur = options.minVideoDuration {
|
|
182
|
-
config.minimumSelectedVideoDuration = minDur
|
|
163
|
+
config.minimumSelectedVideoDuration = Int(minDur)
|
|
183
164
|
}
|
|
184
165
|
|
|
185
|
-
// Editor / crop
|
|
166
|
+
// Editor / crop (only when maxCount == 1)
|
|
186
167
|
let maxCount = Int(options.maxCount ?? 1)
|
|
187
168
|
if let crop = options.crop, crop.enabled, maxCount == 1 {
|
|
188
169
|
config.editorOptions = [.photo]
|
|
189
170
|
var editorConfig = EditorConfiguration()
|
|
190
171
|
|
|
191
|
-
var cropSizeConfig = EditorConfiguration.Photo.CropSize()
|
|
192
172
|
if crop.circular == true {
|
|
193
|
-
|
|
194
|
-
cropSizeConfig.isRoundCrop = true
|
|
173
|
+
editorConfig.cropSize.isRoundCrop = true
|
|
195
174
|
} else if crop.freeStyle == true {
|
|
196
|
-
|
|
175
|
+
editorConfig.cropSize.isFixedRatio = false
|
|
197
176
|
} else {
|
|
198
177
|
let x = crop.ratioX ?? 1.0
|
|
199
178
|
let y = crop.ratioY ?? 1.0
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
]
|
|
179
|
+
editorConfig.cropSize.isFixedRatio = true
|
|
180
|
+
editorConfig.cropSize.aspectRatio = .init(width: x, height: y)
|
|
203
181
|
}
|
|
204
|
-
editorConfig.photo.cropSize = cropSizeConfig
|
|
205
182
|
config.editor = editorConfig
|
|
206
183
|
}
|
|
207
184
|
|
|
@@ -210,13 +187,6 @@ final class HybridPictureSelector: HybridHybridPictureSelectorSpec_base, HybridH
|
|
|
210
187
|
config.themeColor = color
|
|
211
188
|
}
|
|
212
189
|
|
|
213
|
-
// selectedAssets: pre-selecting assets by file:// URI requires resolving each URI
|
|
214
|
-
// back to a PHAsset via PHPhotoLibrary and wrapping it in a PhotoAsset object.
|
|
215
|
-
// This is not yet implemented. Callers should not rely on this option on iOS.
|
|
216
|
-
// TODO: implement pre-selection using PHAsset.fetchAssets(withALAssetURLs:options:)
|
|
217
|
-
// or localIdentifier lookup, then set config.preSelectedAssets (verify field name
|
|
218
|
-
// in HXPhotoPicker v5.0.5 before enabling).
|
|
219
|
-
|
|
220
190
|
return config
|
|
221
191
|
}
|
|
222
192
|
|
|
@@ -243,8 +213,6 @@ final class HybridPictureSelector: HybridHybridPictureSelectorSpec_base, HybridH
|
|
|
243
213
|
compress: CompressOptions?,
|
|
244
214
|
isOriginal: Bool
|
|
245
215
|
) async throws -> MediaAsset {
|
|
246
|
-
// Obtain file URL via callback, bridged to async/await.
|
|
247
|
-
// API REQUIRES VERIFICATION: getURL(compression:result:) availability in v5.0.5.
|
|
248
216
|
let urlResult: AssetURLResult = try await withCheckedThrowingContinuation { cont in
|
|
249
217
|
photoAsset.getURL(
|
|
250
218
|
compression: buildCompression(from: compress)
|
|
@@ -256,22 +224,23 @@ final class HybridPictureSelector: HybridHybridPictureSelectorSpec_base, HybridH
|
|
|
256
224
|
}
|
|
257
225
|
}
|
|
258
226
|
|
|
259
|
-
// Determine if the user applied edits
|
|
260
227
|
let wasEdited = photoAsset.editedResult != nil
|
|
261
228
|
let finalUri = urlResult.url.absoluteString
|
|
262
229
|
let editedUri: String? = wasEdited ? finalUri : nil
|
|
263
230
|
|
|
264
|
-
// Image / video dimensions
|
|
265
|
-
// API REQUIRES VERIFICATION: imageSize property name in v5.0.5
|
|
266
231
|
let size: CGSize = photoAsset.imageSize
|
|
267
232
|
|
|
268
|
-
//
|
|
269
|
-
// API REQUIRES VERIFICATION: videoDuration property name and unit.
|
|
233
|
+
// HXPhotoPicker returns seconds; bridge spec expects ms.
|
|
270
234
|
let durationMs: Double = (photoAsset.videoDuration ?? 0) * 1_000
|
|
271
235
|
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
let
|
|
236
|
+
// AssetURLResult has no fileSize; read from disk.
|
|
237
|
+
let fileSize: Double
|
|
238
|
+
if let attrs = try? FileManager.default.attributesOfItem(atPath: urlResult.url.path),
|
|
239
|
+
let bytes = attrs[.size] as? UInt64 {
|
|
240
|
+
fileSize = Double(bytes)
|
|
241
|
+
} else {
|
|
242
|
+
fileSize = 0
|
|
243
|
+
}
|
|
275
244
|
|
|
276
245
|
let typeStr: String = (photoAsset.mediaType == .video) ? "video" : "image"
|
|
277
246
|
|
|
@@ -292,7 +261,6 @@ final class HybridPictureSelector: HybridHybridPictureSelectorSpec_base, HybridH
|
|
|
292
261
|
|
|
293
262
|
// MARK: - Compression helper
|
|
294
263
|
|
|
295
|
-
/// API REQUIRES VERIFICATION: PhotoAsset.Compression type name and init params in v5.0.5.
|
|
296
264
|
private func buildCompression(from options: CompressOptions?) -> PhotoAsset.Compression? {
|
|
297
265
|
guard let opts = options, opts.enabled else { return nil }
|
|
298
266
|
return PhotoAsset.Compression(
|
|
@@ -345,7 +313,6 @@ extension HybridPictureSelector: PhotoPickerControllerDelegate {
|
|
|
345
313
|
_ pickerController: PhotoPickerController,
|
|
346
314
|
didFinishSelection result: PickerResult
|
|
347
315
|
) {
|
|
348
|
-
// Capture and clear session atomically before dismiss completes
|
|
349
316
|
let captured = session
|
|
350
317
|
session = nil
|
|
351
318
|
activePicker = nil
|
|
@@ -353,15 +320,15 @@ extension HybridPictureSelector: PhotoPickerControllerDelegate {
|
|
|
353
320
|
pickerController.dismiss(animated: true) { [weak self] in
|
|
354
321
|
guard let s = captured else { return }
|
|
355
322
|
guard let self else {
|
|
356
|
-
s.
|
|
323
|
+
s.promise.reject(withError: PictureSelectorError.unknown("pickerController: native module was deallocated"))
|
|
357
324
|
return
|
|
358
325
|
}
|
|
359
326
|
Task {
|
|
360
327
|
do {
|
|
361
328
|
let assets = try await self.mapResults(result, compress: s.options.compress)
|
|
362
|
-
s.
|
|
329
|
+
s.promise.resolve(withResult: assets)
|
|
363
330
|
} catch {
|
|
364
|
-
s.
|
|
331
|
+
s.promise.reject(withError: error)
|
|
365
332
|
}
|
|
366
333
|
}
|
|
367
334
|
}
|
|
@@ -373,7 +340,7 @@ extension HybridPictureSelector: PhotoPickerControllerDelegate {
|
|
|
373
340
|
activePicker = nil
|
|
374
341
|
|
|
375
342
|
pickerController.dismiss(animated: true) {
|
|
376
|
-
captured?.
|
|
343
|
+
captured?.promise.reject(withError: PictureSelectorError.cancelled)
|
|
377
344
|
}
|
|
378
345
|
}
|
|
379
346
|
}
|
|
@@ -397,9 +364,6 @@ enum PictureSelectorError: Error, LocalizedError {
|
|
|
397
364
|
// MARK: - Nitro registration helper
|
|
398
365
|
|
|
399
366
|
/// Called from NitroPictureSelectorOnLoad.mm at startup.
|
|
400
|
-
/// Creates a HybridPictureSelector instance and returns a retained raw pointer
|
|
401
|
-
/// to its HybridHybridPictureSelectorSpec_cxx wrapper.
|
|
402
|
-
/// The caller (C++ factory) takes ownership via create_std__shared_ptr_HybridHybridPictureSelectorSpec_.
|
|
403
367
|
@_cdecl("NitroPictureSelectorMakeHybrid")
|
|
404
368
|
public func NitroPictureSelectorMakeHybrid() -> UnsafeMutableRawPointer {
|
|
405
369
|
HybridPictureSelector().getCxxWrapper().toUnsafe()
|
package/package.json
CHANGED