@imgly/camera-react-native 1.40.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/IMGLYCameraModule.podspec +21 -0
- package/LICENSE.md +137 -0
- package/README.md +26 -0
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +64 -0
- package/android/src/main/kotlin/ly/img/camera/reactnative/module/IMGLYCameraModule.kt +249 -0
- package/android/src/main/kotlin/ly/img/camera/reactnative/module/IMGLYCameraPackage.kt +35 -0
- package/android/src/main/kotlin/ly/img/camera/reactnative/module/model/CameraResult.kt +118 -0
- package/android/src/main/kotlin/ly/img/camera/reactnative/module/model/CameraSettings.kt +46 -0
- package/android/src/main/kotlin/ly/img/camera/reactnative/module/model/Recording.kt +83 -0
- package/android/src/newarch/IMGLYCameraModuleSpec.kt +7 -0
- package/android/src/oldarch/IMGLYEditorModuleSpec.kt +17 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/ios/IMGLYCameraModule.h +11 -0
- package/ios/IMGLYCameraModule.mm +61 -0
- package/ios/IMGLYCameraModule.swift +70 -0
- package/ios/Model/CameraBuilder.swift +94 -0
- package/ios/Model/CameraResult.swift +181 -0
- package/ios/Model/CameraSettings.swift +24 -0
- package/package.json +46 -0
- package/src/NativeIMGLYCamera.ts +191 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
package ly.img.camera.reactnative.module.model
|
|
2
|
+
|
|
3
|
+
import android.os.Parcel
|
|
4
|
+
import android.os.Parcelable
|
|
5
|
+
import ly.img.camera.reactnative.module.model.CameraResult.CREATOR.readMap
|
|
6
|
+
import ly.img.camera.reactnative.module.model.CameraResult.CREATOR.writeMap
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The result type for a camera recording session in standard mode.
|
|
10
|
+
*
|
|
11
|
+
* @property recordings The recordings of the camera session.
|
|
12
|
+
*/
|
|
13
|
+
data class CameraRecording(
|
|
14
|
+
val recordings: List<Recording>?,
|
|
15
|
+
) : Parcelable {
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new instance from a given [Parcel].
|
|
18
|
+
* @param parcel The [Parcel].
|
|
19
|
+
*/
|
|
20
|
+
constructor(parcel: Parcel) : this(
|
|
21
|
+
parcel.createTypedArrayList(Recording),
|
|
22
|
+
)
|
|
23
|
+
constructor(record: ly.img.camera.core.CameraResult.Record) : this(
|
|
24
|
+
record.recordings.map { Recording(it) },
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
override fun writeToParcel(
|
|
28
|
+
parcel: Parcel,
|
|
29
|
+
flags: Int,
|
|
30
|
+
) {
|
|
31
|
+
parcel.writeTypedList(recordings)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun describeContents(): Int = 0
|
|
35
|
+
|
|
36
|
+
companion object CREATOR : Parcelable.Creator<CameraResult> {
|
|
37
|
+
override fun createFromParcel(parcel: Parcel): CameraResult = CameraResult(parcel)
|
|
38
|
+
|
|
39
|
+
override fun newArray(size: Int): Array<CameraResult?> = arrayOfNulls(size)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fun toMap(): Map<String, Any?> =
|
|
43
|
+
mapOf(
|
|
44
|
+
"recordings" to recordings?.map { it.toMap() },
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A class representing the result of a camera session.
|
|
50
|
+
*
|
|
51
|
+
* @property recording The result for the camera session in standard mode.
|
|
52
|
+
* @property metadata The associated metadata.
|
|
53
|
+
*/
|
|
54
|
+
data class CameraResult(
|
|
55
|
+
val recording: CameraRecording?,
|
|
56
|
+
val metadata: Map<String, Any> = emptyMap(),
|
|
57
|
+
) : Parcelable {
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new instance from a given [Parcel].
|
|
60
|
+
* @param parcel The [Parcel].
|
|
61
|
+
*/
|
|
62
|
+
constructor(parcel: Parcel) : this(
|
|
63
|
+
CameraRecording(parcel),
|
|
64
|
+
readMap(parcel),
|
|
65
|
+
)
|
|
66
|
+
constructor(record: ly.img.camera.core.CameraResult.Record) : this(
|
|
67
|
+
CameraRecording(record),
|
|
68
|
+
emptyMap(),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
override fun writeToParcel(
|
|
72
|
+
parcel: Parcel,
|
|
73
|
+
flags: Int,
|
|
74
|
+
) {
|
|
75
|
+
recording?.writeToParcel(parcel, flags)
|
|
76
|
+
writeMap(parcel, metadata, flags)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override fun describeContents(): Int = 0
|
|
80
|
+
|
|
81
|
+
companion object CREATOR : Parcelable.Creator<CameraResult> {
|
|
82
|
+
override fun createFromParcel(parcel: Parcel): CameraResult = CameraResult(parcel)
|
|
83
|
+
|
|
84
|
+
override fun newArray(size: Int): Array<CameraResult?> = arrayOfNulls(size)
|
|
85
|
+
|
|
86
|
+
private fun writeMap(
|
|
87
|
+
parcel: Parcel,
|
|
88
|
+
map: Map<String, Any>,
|
|
89
|
+
flags: Int,
|
|
90
|
+
) {
|
|
91
|
+
parcel.writeInt(map.size)
|
|
92
|
+
for ((key, value) in map) {
|
|
93
|
+
parcel.writeString(key)
|
|
94
|
+
parcel.writeValue(value)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private fun readMap(parcel: Parcel): Map<String, Any> {
|
|
99
|
+
val size = parcel.readInt()
|
|
100
|
+
val map = mutableMapOf<String, Any>()
|
|
101
|
+
repeat(size) {
|
|
102
|
+
val key = parcel.readString()
|
|
103
|
+
val value = parcel.readValue(this::class.java.classLoader)
|
|
104
|
+
key ?: throw (IllegalStateException("Key must not be null."))
|
|
105
|
+
value ?: throw (IllegalStateException("Value must not be null."))
|
|
106
|
+
|
|
107
|
+
map[key] = value
|
|
108
|
+
}
|
|
109
|
+
return map
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fun toMap(): Map<String, Any?> =
|
|
114
|
+
mapOf(
|
|
115
|
+
"recording" to recording?.toMap(),
|
|
116
|
+
"metadata" to metadata,
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package ly.img.camera.reactnative.module.model
|
|
2
|
+
|
|
3
|
+
import android.os.Parcel
|
|
4
|
+
import android.os.Parcelable
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A class containing all necessary information to configure the camera.
|
|
8
|
+
*
|
|
9
|
+
* @property license The license key.
|
|
10
|
+
* @property userId The id of the current user.
|
|
11
|
+
*/
|
|
12
|
+
data class CameraSettings(
|
|
13
|
+
val license: String,
|
|
14
|
+
val userId: String?,
|
|
15
|
+
) : Parcelable {
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new instance from a given [Parcel].
|
|
18
|
+
* @param parcel The [Parcel].
|
|
19
|
+
*/
|
|
20
|
+
constructor(parcel: Parcel) : this(
|
|
21
|
+
parcel.readString() ?: throw (Exception("Missing value for key 'license.'")),
|
|
22
|
+
parcel.readString(),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
override fun writeToParcel(
|
|
26
|
+
parcel: Parcel,
|
|
27
|
+
flags: Int,
|
|
28
|
+
) {
|
|
29
|
+
parcel.writeString(license)
|
|
30
|
+
parcel.writeString(userId)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override fun describeContents(): Int = 0
|
|
34
|
+
|
|
35
|
+
companion object CREATOR : Parcelable.Creator<CameraSettings> {
|
|
36
|
+
override fun createFromParcel(parcel: Parcel): CameraSettings = CameraSettings(parcel)
|
|
37
|
+
|
|
38
|
+
override fun newArray(size: Int): Array<CameraSettings?> = arrayOfNulls(size)
|
|
39
|
+
|
|
40
|
+
fun createFromMap(map: Map<String, Any>): CameraSettings {
|
|
41
|
+
val license = map["license"] as? String ?: throw (Exception("Missing value for key 'license.'"))
|
|
42
|
+
val userId = map["userId"] as? String
|
|
43
|
+
return CameraSettings(license, userId)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
package ly.img.camera.reactnative.module.model
|
|
2
|
+
|
|
3
|
+
import android.net.Uri
|
|
4
|
+
import android.os.Parcel
|
|
5
|
+
import android.os.Parcelable
|
|
6
|
+
import kotlin.time.Duration
|
|
7
|
+
import kotlin.time.Duration.Companion.milliseconds
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A camera recording.
|
|
11
|
+
*
|
|
12
|
+
* @property videos contains one or two [Video]s, for single camera output or dual camera output respectively
|
|
13
|
+
* @property duration the duration of the recording
|
|
14
|
+
*/
|
|
15
|
+
data class Recording(
|
|
16
|
+
val videos: List<Video>,
|
|
17
|
+
val duration: Duration,
|
|
18
|
+
) : Parcelable {
|
|
19
|
+
constructor(parcel: Parcel) : this(
|
|
20
|
+
videos = parcel.createTypedArrayList(Video)!!,
|
|
21
|
+
duration = parcel.readLong().milliseconds,
|
|
22
|
+
)
|
|
23
|
+
constructor(recording: ly.img.camera.core.Recording) : this(
|
|
24
|
+
recording.videos.map { Video(it) },
|
|
25
|
+
recording.duration,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
override fun writeToParcel(
|
|
29
|
+
parcel: Parcel,
|
|
30
|
+
flags: Int,
|
|
31
|
+
) {
|
|
32
|
+
parcel.writeTypedList(videos)
|
|
33
|
+
parcel.writeLong(duration.inWholeMilliseconds)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override fun describeContents(): Int = 0
|
|
37
|
+
|
|
38
|
+
companion object CREATOR : Parcelable.Creator<Recording> {
|
|
39
|
+
override fun createFromParcel(parcel: Parcel): Recording = Recording(parcel)
|
|
40
|
+
|
|
41
|
+
override fun newArray(size: Int): Array<Recording?> = arrayOfNulls(size)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fun toMap(): Map<String, Any?> =
|
|
45
|
+
mapOf(
|
|
46
|
+
"videos" to videos.map { it.toMap() },
|
|
47
|
+
"duration" to duration.inWholeMilliseconds.toDouble(),
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A video in a [Recording].
|
|
53
|
+
*
|
|
54
|
+
* @param uri the uri of the video
|
|
55
|
+
*/
|
|
56
|
+
data class Video(
|
|
57
|
+
val uri: Uri,
|
|
58
|
+
) : Parcelable {
|
|
59
|
+
constructor(parcel: Parcel) : this(uri = parcel.readParcelable(Uri::class.java.classLoader)!!)
|
|
60
|
+
constructor(video: ly.img.camera.core.Video) : this(
|
|
61
|
+
video.uri,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
override fun writeToParcel(
|
|
65
|
+
parcel: Parcel,
|
|
66
|
+
flags: Int,
|
|
67
|
+
) {
|
|
68
|
+
parcel.writeParcelable(uri, flags)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override fun describeContents(): Int = 0
|
|
72
|
+
|
|
73
|
+
companion object CREATOR : Parcelable.Creator<Video> {
|
|
74
|
+
override fun createFromParcel(parcel: Parcel): Video = Video(parcel)
|
|
75
|
+
|
|
76
|
+
override fun newArray(size: Int): Array<Video?> = arrayOfNulls(size)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fun toMap(): Map<String, Any?> =
|
|
80
|
+
mapOf(
|
|
81
|
+
"uri" to uri.toString(),
|
|
82
|
+
)
|
|
83
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package ly.img.camera.reactnative.module
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Promise
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
+
import com.facebook.react.bridge.ReadableMap
|
|
7
|
+
|
|
8
|
+
abstract class IMGLYCameraModuleSpec internal constructor(
|
|
9
|
+
context: ReactApplicationContext,
|
|
10
|
+
) : ReactContextBaseJavaModule(context) {
|
|
11
|
+
abstract fun openCamera(
|
|
12
|
+
settings: ReadableMap?,
|
|
13
|
+
video: String?,
|
|
14
|
+
metadata: ReadableMap?,
|
|
15
|
+
promise: Promise?,
|
|
16
|
+
)
|
|
17
|
+
}
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
2
|
+
#import <IMGLYCameraModuleSpec/IMGLYCameraModuleSpec.h>
|
|
3
|
+
|
|
4
|
+
@interface IMGLYCameraModule : NSObject <NativeIMGLYCameraSpec>
|
|
5
|
+
#else
|
|
6
|
+
#import <React/RCTBridgeModule.h>
|
|
7
|
+
|
|
8
|
+
@interface IMGLYCameraModule : NSObject <RCTBridgeModule>
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
@end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#import "IMGLYCameraModule.h"
|
|
2
|
+
#import "IMGLYCameraModule/IMGLYCameraModule-Swift.h"
|
|
3
|
+
|
|
4
|
+
@implementation IMGLYCameraModule
|
|
5
|
+
|
|
6
|
+
RCT_EXPORT_MODULE(IMGLYCamera)
|
|
7
|
+
|
|
8
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
9
|
+
- (void)openCamera:(JS::NativeIMGLYCamera::CameraSettings&)settings
|
|
10
|
+
video:(NSString*)video
|
|
11
|
+
metadata:(NSDictionary*)metadata
|
|
12
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
13
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
14
|
+
NSMutableDictionary* convertedSettings = [@{
|
|
15
|
+
@"license" : settings.license(),
|
|
16
|
+
} mutableCopy];
|
|
17
|
+
if (settings.userId() != nil) {
|
|
18
|
+
convertedSettings[@"userId"] = settings.userId();
|
|
19
|
+
}
|
|
20
|
+
[self open:convertedSettings video:video metadata:metadata resolve:resolve reject:reject];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
24
|
+
(const facebook::react::ObjCTurboModule::InitParams&)params {
|
|
25
|
+
return std::make_shared<facebook::react::NativeIMGLYCameraSpecJSI>(params);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#else
|
|
29
|
+
RCT_EXPORT_METHOD(openCamera
|
|
30
|
+
: (nonnull NSDictionary*)settings video
|
|
31
|
+
: (nullable NSString*)video metadata
|
|
32
|
+
: (NSDictionary*)metadata resolve
|
|
33
|
+
: (RCTPromiseResolveBlock)resolve reject
|
|
34
|
+
: (RCTPromiseRejectBlock)reject) {
|
|
35
|
+
[self open:settings video:video metadata:metadata resolve:resolve reject:reject];
|
|
36
|
+
}
|
|
37
|
+
#endif
|
|
38
|
+
|
|
39
|
+
- (void)open:(nonnull NSDictionary*)settings
|
|
40
|
+
video:(nullable NSString*)video
|
|
41
|
+
metadata:(NSDictionary*)metadata
|
|
42
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
43
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
44
|
+
CameraSettings* cameraSettings = [CameraSettings fromDictionary:settings];
|
|
45
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
46
|
+
[[IMGLYCameraModuleSwiftAdapter shared]
|
|
47
|
+
openCameraWithSettings:cameraSettings
|
|
48
|
+
video:video
|
|
49
|
+
metadata:metadata
|
|
50
|
+
completion:^(CameraResult* _Nullable result, NSError* _Nullable error) {
|
|
51
|
+
if (result != nil) {
|
|
52
|
+
resolve([result toDictionary]);
|
|
53
|
+
} else if (error != nil) {
|
|
54
|
+
reject(@"E_RECORDING_FAILED", @"The recording failed due to: ", error);
|
|
55
|
+
} else {
|
|
56
|
+
resolve([NSNull null]);
|
|
57
|
+
}
|
|
58
|
+
}];
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
@end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import IMGLYCamera
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// The iOS implementation for the `@imgly/camera-react-native` native React Native module.
|
|
5
|
+
@objc(IMGLYCameraModuleSwiftAdapter) @objcMembers public class IMGLYCameraModuleSwiftAdapter: NSObject {
|
|
6
|
+
// MARK: - Typealias
|
|
7
|
+
|
|
8
|
+
/// A closure to specify an `CameraBuilder.Builder` based on a given `metadata`.
|
|
9
|
+
public typealias CameraBuilderClosure = (_ metadata: [String: Any]?) -> CameraBuilder.Builder
|
|
10
|
+
|
|
11
|
+
// MARK: - Properties
|
|
12
|
+
|
|
13
|
+
/// The shared instance.
|
|
14
|
+
public static var shared = IMGLYCameraModuleSwiftAdapter()
|
|
15
|
+
|
|
16
|
+
/// The `CameraBuilderClosure` to use for UI creation.
|
|
17
|
+
public var cameraBuilderClosure: CameraBuilderClosure?
|
|
18
|
+
|
|
19
|
+
/// The `UIViewController` hosting the editor.
|
|
20
|
+
private var presentationController: UIViewController?
|
|
21
|
+
|
|
22
|
+
/// Opens the camera.
|
|
23
|
+
/// - Parameters:
|
|
24
|
+
/// - settings: The `CameraSettings` containing all relevant information for the editor.
|
|
25
|
+
/// - video: The source of the video to react to.
|
|
26
|
+
/// - metadata: Any custom metadata used for the `CameraBuilder.Builder`.
|
|
27
|
+
/// - completion: The completion handler to execute once the camera failed, cancelled or exported.
|
|
28
|
+
public func openCamera(
|
|
29
|
+
settings: CameraSettings,
|
|
30
|
+
video: String?,
|
|
31
|
+
metadata: [String: Any]?,
|
|
32
|
+
completion: @escaping (_ result: CameraResult?, _ error: NSError?) -> Void
|
|
33
|
+
) {
|
|
34
|
+
var videoURL: URL?
|
|
35
|
+
if let video {
|
|
36
|
+
if let sourceUrl = URL(string: video), sourceUrl.scheme != nil {
|
|
37
|
+
videoURL = sourceUrl
|
|
38
|
+
} else {
|
|
39
|
+
completion(nil, nil)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let builder = cameraBuilderClosure?(metadata) ?? CameraBuilder.default()
|
|
44
|
+
presentationController = builder(settings, videoURL, metadata) { [weak self] result in
|
|
45
|
+
DispatchQueue.main.async {
|
|
46
|
+
switch result {
|
|
47
|
+
case let .success(artifact):
|
|
48
|
+
completion(artifact, nil)
|
|
49
|
+
case let .failure(error):
|
|
50
|
+
switch error {
|
|
51
|
+
case CameraError.cancelled:
|
|
52
|
+
completion(nil, nil)
|
|
53
|
+
default:
|
|
54
|
+
completion(nil, error as NSError)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
self?.presentationController?.presentingViewController?.dismiss(animated: true)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
presentationController?.modalPresentationStyle = .fullScreen
|
|
61
|
+
if let presentationController, let windowScene = UIApplication.shared.connectedScenes
|
|
62
|
+
.filter({ $0.activationState == .foregroundActive })
|
|
63
|
+
.first as? UIWindowScene {
|
|
64
|
+
if let rootViewController = windowScene.windows
|
|
65
|
+
.filter(\.isKeyWindow).first?.rootViewController {
|
|
66
|
+
rootViewController.present(presentationController, animated: true, completion: nil)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import IMGLYCamera
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
public enum CameraBuilder {
|
|
5
|
+
/// A closure used to return a `Result<CameraResult?, Error>` from the camera.
|
|
6
|
+
public typealias CameraBuilderResult = (Result<CameraResult?, Error>) -> Void
|
|
7
|
+
|
|
8
|
+
/// Closure allowing to return a custom UI based on various input.
|
|
9
|
+
/// - Parameters:
|
|
10
|
+
/// - settings: The `CameraSettings`.
|
|
11
|
+
/// - url: The `URL` of the video to react to.
|
|
12
|
+
/// - metadata: The metadata.
|
|
13
|
+
/// - result: The `CameraBuilderResult` used to return a result.
|
|
14
|
+
/// - Returns: A `UIViewController`.
|
|
15
|
+
public typealias Builder = (
|
|
16
|
+
_ settings: CameraSettings,
|
|
17
|
+
_ url: URL?,
|
|
18
|
+
_ metadata: [String: Any]?,
|
|
19
|
+
_ result: @escaping CameraBuilderResult
|
|
20
|
+
) -> UIViewController
|
|
21
|
+
|
|
22
|
+
/// The default camera implementation.
|
|
23
|
+
public static func `default`() -> Builder {
|
|
24
|
+
{ settings, url, _, result in
|
|
25
|
+
@ViewBuilder func camera(settings: CameraSettings, result: @escaping CameraBuilderResult) -> some View {
|
|
26
|
+
ModalCamera(settings: settings, result: result, url: url)
|
|
27
|
+
}
|
|
28
|
+
return UIHostingController(rootView: camera(settings: settings, result: result))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// A custom camera implementation.
|
|
33
|
+
/// - Parameter contentProvider: Used to provide a custom view.
|
|
34
|
+
/// - Returns: A `Builder` providing the custom view.
|
|
35
|
+
public static func custom(_ contentProvider: @escaping (
|
|
36
|
+
_ settings: CameraSettings,
|
|
37
|
+
_ url: URL?,
|
|
38
|
+
_ metadata: [String: Any]?,
|
|
39
|
+
_ result: @escaping CameraBuilderResult
|
|
40
|
+
) -> some View) -> Builder {
|
|
41
|
+
{ settings, preset, metadata, result in
|
|
42
|
+
let hostingController = UIHostingController(rootView: contentProvider(settings, preset, metadata, result))
|
|
43
|
+
return hostingController
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
extension CameraBuilder {
|
|
49
|
+
/// Generates `EngineSettings` based on an `CameraSettings`.
|
|
50
|
+
/// - Parameter settings: The `CameraSettings`.
|
|
51
|
+
/// - Returns: The derived `EngineSettings`.
|
|
52
|
+
private static func engineSettings(for settings: CameraSettings) -> EngineSettings {
|
|
53
|
+
EngineSettings(license: settings.license, userID: settings.userId)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// A modal camera implementation.
|
|
57
|
+
public struct ModalCamera: View {
|
|
58
|
+
private let settings: CameraSettings
|
|
59
|
+
private let result: CameraBuilderResult
|
|
60
|
+
private let url: URL?
|
|
61
|
+
private let onDismiss: (Result<IMGLYCamera.CameraResult, CameraError>) -> Void
|
|
62
|
+
|
|
63
|
+
public init(settings: CameraSettings, result: @escaping CameraBuilderResult, url: URL? = nil) {
|
|
64
|
+
self.settings = settings
|
|
65
|
+
self.result = result
|
|
66
|
+
self.url = url
|
|
67
|
+
|
|
68
|
+
onDismiss = { cameraResult in
|
|
69
|
+
switch cameraResult {
|
|
70
|
+
case let .success(.reaction(video: recording, reaction: reaction)):
|
|
71
|
+
let reaction = CameraReaction(video: recording, recordings: reaction)
|
|
72
|
+
result(.success(CameraResult(reaction: reaction)))
|
|
73
|
+
case let .success(.recording(recording)):
|
|
74
|
+
let recording = CameraRecording(recordings: recording)
|
|
75
|
+
result(.success(CameraResult(recording: recording)))
|
|
76
|
+
case let .failure(error):
|
|
77
|
+
result(.failure(error))
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public var body: some View {
|
|
83
|
+
if let url {
|
|
84
|
+
Camera(
|
|
85
|
+
engineSettings(for: settings),
|
|
86
|
+
mode: .reaction(.horizontal, video: url, positionsSwapped: false),
|
|
87
|
+
onDismiss: onDismiss
|
|
88
|
+
)
|
|
89
|
+
} else {
|
|
90
|
+
Camera(engineSettings(for: settings), onDismiss: onDismiss)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|