@imgly/editor-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/IMGLYEditorModule.podspec +21 -0
- package/LICENSE.md +137 -0
- package/README.md +31 -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 +65 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/assets/photo-ui-empty.png +0 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/IMGLYEditorModule.kt +252 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/IMGLYEditorPackage.kt +35 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/activity/EditorActivity.kt +112 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/builder/EditorBuilder.kt +478 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/helper/AssetHelper.kt +18 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/helper/IntentHelper.kt +61 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/model/EditorPreset.kt +47 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/model/EditorResult.kt +73 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/model/EditorSettings.kt +63 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/model/EditorSourceType.kt +39 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/model/Source.kt +44 -0
- package/android/src/newarch/IMGLYEditorModuleSpec.kt +7 -0
- package/android/src/oldarch/IMGLYEditorModuleSpec.kt +18 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/ios/Constants/IMGLYConstants.h +13 -0
- package/ios/Constants/IMGLYConstants.m +13 -0
- package/ios/Extensions/OnCreate+.swift +75 -0
- package/ios/Extensions/OnExport+.swift +64 -0
- package/ios/Extensions/String+.swift +7 -0
- package/ios/Helper/ExportHelper.swift +67 -0
- package/ios/IMGLYEditorModule-Bridging-Header.h +3 -0
- package/ios/IMGLYEditorModule.h +11 -0
- package/ios/IMGLYEditorModule.mm +86 -0
- package/ios/IMGLYEditorModule.swift +110 -0
- package/ios/Model/EditorBuilder.swift +293 -0
- package/ios/Model/EditorPreset.swift +52 -0
- package/ios/Model/EditorResult.swift +27 -0
- package/ios/Model/EditorSettings.swift +33 -0
- package/ios/Model/EditorSourceType.swift +11 -0
- package/ios/Model/Source.swift +8 -0
- package/ios/View/ModalEditor.swift +55 -0
- package/package.json +46 -0
- package/src/NativeIMGLYEditor.ts +195 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package ly.img.editor.reactnative.module.activity
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.os.Bundle
|
|
7
|
+
import android.os.Parcelable
|
|
8
|
+
import androidx.activity.ComponentActivity
|
|
9
|
+
import androidx.activity.compose.setContent
|
|
10
|
+
import ly.img.editor.reactnative.module.IMGLYEditorModule
|
|
11
|
+
import ly.img.editor.reactnative.module.builder.Builder
|
|
12
|
+
import ly.img.editor.reactnative.module.builder.CustomBuilderScope
|
|
13
|
+
import ly.img.editor.reactnative.module.builder.EditorBuilder
|
|
14
|
+
import ly.img.editor.reactnative.module.helper.IntentHelper
|
|
15
|
+
import ly.img.editor.reactnative.module.model.EditorPreset
|
|
16
|
+
import ly.img.editor.reactnative.module.model.EditorSettings
|
|
17
|
+
import java.io.Serializable
|
|
18
|
+
|
|
19
|
+
/** The [Activity] used to display the editor. */
|
|
20
|
+
class EditorActivity : ComponentActivity() {
|
|
21
|
+
companion object {
|
|
22
|
+
/** The request code for this activity. */
|
|
23
|
+
var INTENT_REQUEST_CODE = 29055
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The result code for this activity in
|
|
27
|
+
* case of an error.
|
|
28
|
+
*/
|
|
29
|
+
var INTENT_RESULT_ERROR_CODE = 29056
|
|
30
|
+
|
|
31
|
+
/** The identifier to the config intent extra. */
|
|
32
|
+
const val INTENT_EXTRA_CONFIG = "intent_extra_config_cesdk"
|
|
33
|
+
|
|
34
|
+
/** The identifier to the preset intent extra. */
|
|
35
|
+
const val INTENT_EXTRA_PRESET = "intent_extra_preset_cesdk"
|
|
36
|
+
|
|
37
|
+
/** The identifier to the metadata intent extra. */
|
|
38
|
+
const val INTENT_EXTRA_METADATA = "intent_extra_metadata_cesdk"
|
|
39
|
+
|
|
40
|
+
/** The identifier to the result intent extra. */
|
|
41
|
+
const val INTENT_EXTRA_RESULT = "intent_extra_result_cesdk"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
45
|
+
super.onCreate(savedInstanceState)
|
|
46
|
+
|
|
47
|
+
val config =
|
|
48
|
+
intent.extras?.getParcelableCompat<EditorSettings>(INTENT_EXTRA_CONFIG)
|
|
49
|
+
?: return
|
|
50
|
+
val preset = intent.extras?.getParcelableCompat<EditorPreset>(INTENT_EXTRA_PRESET) ?: EditorPreset.DESIGN
|
|
51
|
+
val metadataBundle = intent.extras?.getParcelableCompat<Bundle>(INTENT_EXTRA_METADATA)
|
|
52
|
+
val metadata = IntentHelper.bundleToMap(metadataBundle)
|
|
53
|
+
val builder: Builder = IMGLYEditorModule.builderClosure?.invoke(preset, metadata) ?: builderForPreset(preset)
|
|
54
|
+
|
|
55
|
+
setContent {
|
|
56
|
+
builder(
|
|
57
|
+
CustomBuilderScope(config, preset, metadata, { result ->
|
|
58
|
+
val (resolvedResult, resultCode) =
|
|
59
|
+
result.fold(
|
|
60
|
+
onFailure = { error ->
|
|
61
|
+
Pair(error, INTENT_RESULT_ERROR_CODE)
|
|
62
|
+
},
|
|
63
|
+
onSuccess = { value ->
|
|
64
|
+
Pair(value, Activity.RESULT_OK)
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
val resultIntent =
|
|
69
|
+
Intent().apply {
|
|
70
|
+
when (resultCode) {
|
|
71
|
+
INTENT_RESULT_ERROR_CODE -> {
|
|
72
|
+
putExtra(INTENT_EXTRA_RESULT, resolvedResult as Serializable)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
Activity.RESULT_OK -> {
|
|
76
|
+
putExtra(INTENT_EXTRA_RESULT, resolvedResult as Parcelable)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setResult(resultCode, resultIntent)
|
|
82
|
+
finish()
|
|
83
|
+
}, {
|
|
84
|
+
if (it != null) {
|
|
85
|
+
val resultIntent =
|
|
86
|
+
Intent().apply {
|
|
87
|
+
putExtra(INTENT_EXTRA_RESULT, it)
|
|
88
|
+
}
|
|
89
|
+
setResult(INTENT_RESULT_ERROR_CODE, resultIntent)
|
|
90
|
+
}
|
|
91
|
+
finish()
|
|
92
|
+
}),
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private fun builderForPreset(preset: EditorPreset): Builder =
|
|
98
|
+
when (preset) {
|
|
99
|
+
EditorPreset.APPAREL -> EditorBuilder.apparel()
|
|
100
|
+
EditorPreset.POSTCARD -> EditorBuilder.postcard()
|
|
101
|
+
EditorPreset.PHOTO -> EditorBuilder.photo()
|
|
102
|
+
EditorPreset.DESIGN -> EditorBuilder.design()
|
|
103
|
+
EditorPreset.VIDEO -> EditorBuilder.video()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private inline fun <reified T : Parcelable> Bundle.getParcelableCompat(key: String): T? =
|
|
107
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
108
|
+
getParcelable(key, T::class.java)
|
|
109
|
+
} else {
|
|
110
|
+
getParcelable(key)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
package ly.img.editor.reactnative.module.builder
|
|
2
|
+
|
|
3
|
+
import android.net.Uri
|
|
4
|
+
import androidx.compose.runtime.Composable
|
|
5
|
+
import kotlinx.coroutines.Dispatchers
|
|
6
|
+
import kotlinx.coroutines.Job
|
|
7
|
+
import kotlinx.coroutines.coroutineScope
|
|
8
|
+
import kotlinx.coroutines.flow.toList
|
|
9
|
+
import kotlinx.coroutines.launch
|
|
10
|
+
import kotlinx.coroutines.withContext
|
|
11
|
+
import ly.img.editor.ApparelEditor
|
|
12
|
+
import ly.img.editor.DesignEditor
|
|
13
|
+
import ly.img.editor.DismissVideoExportEvent
|
|
14
|
+
import ly.img.editor.EditorDefaults
|
|
15
|
+
import ly.img.editor.EngineConfiguration
|
|
16
|
+
import ly.img.editor.HideLoading
|
|
17
|
+
import ly.img.editor.PhotoEditor
|
|
18
|
+
import ly.img.editor.PostcardEditor
|
|
19
|
+
import ly.img.editor.ShowLoading
|
|
20
|
+
import ly.img.editor.ShowVideoExportProgressEvent
|
|
21
|
+
import ly.img.editor.VideoEditor
|
|
22
|
+
import ly.img.editor.core.event.EditorEventHandler
|
|
23
|
+
import ly.img.editor.core.library.data.TextAssetSource
|
|
24
|
+
import ly.img.editor.core.library.data.TypefaceProvider
|
|
25
|
+
import ly.img.editor.reactnative.module.model.EditorPreset
|
|
26
|
+
import ly.img.editor.reactnative.module.model.EditorResult
|
|
27
|
+
import ly.img.editor.reactnative.module.model.EditorSettings
|
|
28
|
+
import ly.img.editor.reactnative.module.model.EditorSourceType
|
|
29
|
+
import ly.img.engine.DemoAssetSource
|
|
30
|
+
import ly.img.engine.DesignBlock
|
|
31
|
+
import ly.img.engine.DesignBlockType
|
|
32
|
+
import ly.img.engine.Engine
|
|
33
|
+
import ly.img.engine.MimeType
|
|
34
|
+
import ly.img.engine.addDefaultAssetSources
|
|
35
|
+
import ly.img.engine.addDemoAssetSources
|
|
36
|
+
import java.io.File
|
|
37
|
+
import java.util.UUID
|
|
38
|
+
import kotlin.coroutines.cancellation.CancellationException
|
|
39
|
+
|
|
40
|
+
/** A closure used to return a [Result] with type [EditorResult] from the editor. */
|
|
41
|
+
typealias EditorBuilderResult = (Result<EditorResult?>) -> Unit
|
|
42
|
+
|
|
43
|
+
/** Closure allowing to return a custom UI based on various input. */
|
|
44
|
+
typealias Builder = @Composable CustomBuilderScope.() -> Unit
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @property settings The [EditorSettings] to configure the editor.
|
|
48
|
+
* @property preset The [EditorPreset] to open.
|
|
49
|
+
* @property metadata The custom metadata.
|
|
50
|
+
* @property result The [EditorBuilderResult] handling the result.
|
|
51
|
+
* @property onClose The closure to handle editor closing.
|
|
52
|
+
*/
|
|
53
|
+
data class CustomBuilderScope(
|
|
54
|
+
val settings: EditorSettings,
|
|
55
|
+
val preset: EditorPreset,
|
|
56
|
+
val metadata: Map<String, Any>?,
|
|
57
|
+
val result: EditorBuilderResult,
|
|
58
|
+
val onClose: (Throwable?) -> Unit,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
/** The [EditorBuilder] is used to build different editor configurations */
|
|
62
|
+
class EditorBuilder {
|
|
63
|
+
companion object {
|
|
64
|
+
/** The default design editor. */
|
|
65
|
+
fun design(): Builder =
|
|
66
|
+
{
|
|
67
|
+
ModalDesignEditor(settings, result, onClose)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The default postcard editor. */
|
|
71
|
+
fun postcard(): Builder =
|
|
72
|
+
{
|
|
73
|
+
ModalPostcardEditor(settings, result, onClose)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The default apparel editor. */
|
|
77
|
+
fun apparel(): Builder =
|
|
78
|
+
{
|
|
79
|
+
ModalApparelEditor(settings, result, onClose)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The default photo editor. */
|
|
83
|
+
fun photo(): Builder =
|
|
84
|
+
{
|
|
85
|
+
ModalPhotoEditor(settings, result, onClose)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The default video editor. */
|
|
89
|
+
fun video(): Builder =
|
|
90
|
+
{
|
|
91
|
+
ModalVideoEditor(settings, result, onClose)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A custom editor implementation.
|
|
96
|
+
* @param contentProvider A closure to provide a custom view.
|
|
97
|
+
* @return A [Builder] providing the custom view.
|
|
98
|
+
*/
|
|
99
|
+
fun custom(contentProvider: @Composable CustomBuilderScope.() -> Unit): Builder =
|
|
100
|
+
{
|
|
101
|
+
val scope = CustomBuilderScope(settings, preset, metadata, result, onClose)
|
|
102
|
+
scope.contentProvider()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@Composable
|
|
106
|
+
private fun ModalPhotoEditor(
|
|
107
|
+
settings: EditorSettings,
|
|
108
|
+
result: EditorBuilderResult,
|
|
109
|
+
onClose: (Throwable?) -> Unit,
|
|
110
|
+
) {
|
|
111
|
+
val engineConfiguration =
|
|
112
|
+
EngineConfiguration.remember(
|
|
113
|
+
license = settings.license,
|
|
114
|
+
baseUri = Uri.parse(settings.baseUri),
|
|
115
|
+
userId = settings.userId,
|
|
116
|
+
onCreate = {
|
|
117
|
+
EditorBuilderDefaults.onCreate(
|
|
118
|
+
engine = editorContext.engine,
|
|
119
|
+
eventHandler = editorContext.eventHandler,
|
|
120
|
+
settings = settings,
|
|
121
|
+
defaultScene = EditorBuilderDefaults.defaultPhotoUri,
|
|
122
|
+
sourceType = EditorSourceType.IMAGE,
|
|
123
|
+
)
|
|
124
|
+
},
|
|
125
|
+
onExport = {
|
|
126
|
+
val export =
|
|
127
|
+
EditorBuilderDefaults.onExport(
|
|
128
|
+
engine = editorContext.engine,
|
|
129
|
+
eventHandler = editorContext.eventHandler,
|
|
130
|
+
mimeType = MimeType.PNG,
|
|
131
|
+
)
|
|
132
|
+
result(Result.success(export))
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
PhotoEditor(engineConfiguration = engineConfiguration) {
|
|
136
|
+
onClose(it)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@Composable
|
|
141
|
+
private fun ModalApparelEditor(
|
|
142
|
+
settings: EditorSettings,
|
|
143
|
+
result: EditorBuilderResult,
|
|
144
|
+
onClose: (Throwable?) -> Unit,
|
|
145
|
+
) {
|
|
146
|
+
val engineConfiguration =
|
|
147
|
+
EngineConfiguration.remember(
|
|
148
|
+
license = settings.license,
|
|
149
|
+
baseUri = Uri.parse(settings.baseUri),
|
|
150
|
+
userId = settings.userId,
|
|
151
|
+
onCreate = {
|
|
152
|
+
EditorBuilderDefaults.onCreate(
|
|
153
|
+
engine = editorContext.engine,
|
|
154
|
+
eventHandler = editorContext.eventHandler,
|
|
155
|
+
settings = settings,
|
|
156
|
+
defaultScene = EngineConfiguration.defaultApparelSceneUri,
|
|
157
|
+
)
|
|
158
|
+
},
|
|
159
|
+
onExport = {
|
|
160
|
+
val export =
|
|
161
|
+
EditorBuilderDefaults.onExport(
|
|
162
|
+
engine = editorContext.engine,
|
|
163
|
+
eventHandler = editorContext.eventHandler,
|
|
164
|
+
mimeType = MimeType.PDF,
|
|
165
|
+
)
|
|
166
|
+
result(Result.success(export))
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
ApparelEditor(engineConfiguration = engineConfiguration) {
|
|
170
|
+
onClose(it)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@Composable
|
|
175
|
+
private fun ModalDesignEditor(
|
|
176
|
+
settings: EditorSettings,
|
|
177
|
+
result: EditorBuilderResult,
|
|
178
|
+
onClose: (Throwable?) -> Unit,
|
|
179
|
+
) {
|
|
180
|
+
val engineConfiguration =
|
|
181
|
+
EngineConfiguration.remember(
|
|
182
|
+
license = settings.license,
|
|
183
|
+
baseUri = Uri.parse(settings.baseUri),
|
|
184
|
+
userId = settings.userId,
|
|
185
|
+
onCreate = {
|
|
186
|
+
EditorBuilderDefaults.onCreate(
|
|
187
|
+
engine = editorContext.engine,
|
|
188
|
+
eventHandler = editorContext.eventHandler,
|
|
189
|
+
settings = settings,
|
|
190
|
+
defaultScene = EngineConfiguration.defaultDesignSceneUri,
|
|
191
|
+
)
|
|
192
|
+
},
|
|
193
|
+
onExport = {
|
|
194
|
+
val export =
|
|
195
|
+
EditorBuilderDefaults.onExport(
|
|
196
|
+
engine = editorContext.engine,
|
|
197
|
+
eventHandler = editorContext.eventHandler,
|
|
198
|
+
mimeType = MimeType.PDF,
|
|
199
|
+
)
|
|
200
|
+
result(Result.success(export))
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
DesignEditor(engineConfiguration = engineConfiguration) {
|
|
205
|
+
onClose(it)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@Composable
|
|
210
|
+
private fun ModalPostcardEditor(
|
|
211
|
+
settings: EditorSettings,
|
|
212
|
+
result: EditorBuilderResult,
|
|
213
|
+
onClose: (Throwable?) -> Unit,
|
|
214
|
+
) {
|
|
215
|
+
val engineConfiguration =
|
|
216
|
+
EngineConfiguration.remember(
|
|
217
|
+
license = settings.license,
|
|
218
|
+
baseUri = Uri.parse(settings.baseUri),
|
|
219
|
+
userId = settings.userId,
|
|
220
|
+
onCreate = {
|
|
221
|
+
EditorBuilderDefaults.onCreate(
|
|
222
|
+
engine = editorContext.engine,
|
|
223
|
+
eventHandler = editorContext.eventHandler,
|
|
224
|
+
settings = settings,
|
|
225
|
+
defaultScene = EngineConfiguration.defaultPostcardSceneUri,
|
|
226
|
+
)
|
|
227
|
+
},
|
|
228
|
+
onExport = {
|
|
229
|
+
val export =
|
|
230
|
+
EditorBuilderDefaults.onExport(
|
|
231
|
+
engine = editorContext.engine,
|
|
232
|
+
eventHandler = editorContext.eventHandler,
|
|
233
|
+
mimeType = MimeType.PDF,
|
|
234
|
+
)
|
|
235
|
+
result(Result.success(export))
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
PostcardEditor(engineConfiguration = engineConfiguration) {
|
|
240
|
+
onClose(it)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@Composable
|
|
245
|
+
private fun ModalVideoEditor(
|
|
246
|
+
settings: EditorSettings,
|
|
247
|
+
result: EditorBuilderResult,
|
|
248
|
+
onClose: (Throwable?) -> Unit,
|
|
249
|
+
) {
|
|
250
|
+
val engineConfiguration =
|
|
251
|
+
EngineConfiguration.remember(
|
|
252
|
+
license = settings.license,
|
|
253
|
+
baseUri = Uri.parse(settings.baseUri),
|
|
254
|
+
userId = settings.userId,
|
|
255
|
+
onCreate = {
|
|
256
|
+
EditorBuilderDefaults.onCreate(
|
|
257
|
+
engine = editorContext.engine,
|
|
258
|
+
eventHandler = editorContext.eventHandler,
|
|
259
|
+
settings = settings,
|
|
260
|
+
defaultScene = EngineConfiguration.defaultVideoSceneUri,
|
|
261
|
+
)
|
|
262
|
+
},
|
|
263
|
+
onExport = {
|
|
264
|
+
try {
|
|
265
|
+
val export =
|
|
266
|
+
EditorBuilderDefaults.onExportVideo(
|
|
267
|
+
engine = editorContext.engine,
|
|
268
|
+
eventHandler = editorContext.eventHandler,
|
|
269
|
+
mimeType = MimeType.MP4,
|
|
270
|
+
)
|
|
271
|
+
result(Result.success(export))
|
|
272
|
+
} catch (e: Exception) {
|
|
273
|
+
if (e !is CancellationException) {
|
|
274
|
+
result(Result.failure(e))
|
|
275
|
+
} else {
|
|
276
|
+
editorContext.eventHandler.send(DismissVideoExportEvent)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
)
|
|
281
|
+
VideoEditor(engineConfiguration = engineConfiguration) {
|
|
282
|
+
onClose(it)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Default implementations to ease the editor configuration process. */
|
|
289
|
+
object EditorBuilderDefaults {
|
|
290
|
+
/** The default image uri for the photo editor. */
|
|
291
|
+
val defaultPhotoUri: Uri = Uri.parse("file:///android_asset/photo-ui-empty.png")
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Default plugin implementation for the *onCreate* callback.
|
|
295
|
+
* @param engine The [Engine] that should be used.
|
|
296
|
+
* @param eventHandler The [EditorEventHandler] that should be used.
|
|
297
|
+
* @param settings The [EditorSettings] containing the relevant editor information.
|
|
298
|
+
* @param defaultScene The [Uri] of the defaultScene if no [EditorSettings.source] is specified.
|
|
299
|
+
* @param sourceType The [EditorSourceType] defining of which type the source is.
|
|
300
|
+
*/
|
|
301
|
+
suspend fun onCreate(
|
|
302
|
+
engine: Engine,
|
|
303
|
+
eventHandler: EditorEventHandler,
|
|
304
|
+
settings: EditorSettings,
|
|
305
|
+
defaultScene: Uri,
|
|
306
|
+
sourceType: EditorSourceType = EditorSourceType.SCENE,
|
|
307
|
+
) = coroutineScope {
|
|
308
|
+
fun isValidUri(uri: Uri): Boolean =
|
|
309
|
+
try {
|
|
310
|
+
uri.scheme != null && uri.path != null
|
|
311
|
+
} catch (e: Exception) {
|
|
312
|
+
false
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
val uri =
|
|
316
|
+
if (settings.source != null) {
|
|
317
|
+
val sourceUri = Uri.parse(settings.source?.source)
|
|
318
|
+
val isValid = isValidUri(sourceUri)
|
|
319
|
+
if (!isValid) {
|
|
320
|
+
throw IllegalArgumentException("The specified source is not a valid Uri.")
|
|
321
|
+
}
|
|
322
|
+
sourceUri
|
|
323
|
+
} else {
|
|
324
|
+
defaultScene
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
when (settings.source?.type ?: sourceType) {
|
|
328
|
+
EditorSourceType.IMAGE -> {
|
|
329
|
+
engine.scene.createFromImage(uri)
|
|
330
|
+
val graphicBlocks = engine.block.findByType(DesignBlockType.Graphic)
|
|
331
|
+
require(graphicBlocks.size == 1) { "No image found." }
|
|
332
|
+
val graphicBlock = graphicBlocks[0]
|
|
333
|
+
val pages = engine.scene.getPages()
|
|
334
|
+
require(pages.size == 1) { "No image found." }
|
|
335
|
+
val page = pages[0]
|
|
336
|
+
engine.block.setFill(page, engine.block.getFill(graphicBlock))
|
|
337
|
+
engine.block.destroy(graphicBlock)
|
|
338
|
+
}
|
|
339
|
+
EditorSourceType.VIDEO -> {
|
|
340
|
+
engine.scene.createFromVideo(uri)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
else -> {
|
|
344
|
+
engine.scene.load(uri)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
launch {
|
|
349
|
+
val assetBaseUri = settings.assetBaseUri ?: "https://cdn.img.ly/assets/v3"
|
|
350
|
+
val isValid = isValidUri(Uri.parse(assetBaseUri))
|
|
351
|
+
val baseUri = Uri.parse(if (isValid) assetBaseUri else "https://cdn.img.ly/assets/v3")
|
|
352
|
+
engine.addDefaultAssetSources(baseUri = baseUri)
|
|
353
|
+
val excluded = setOf(DemoAssetSource.IMAGE, DemoAssetSource.VIDEO, DemoAssetSource.AUDIO)
|
|
354
|
+
engine.addDemoAssetSources(sceneMode = engine.scene.getMode(), withUploadAssetSources = true, exclude = excluded)
|
|
355
|
+
val defaultTypeface = TypefaceProvider().provideTypeface(engine, "Roboto")
|
|
356
|
+
requireNotNull(defaultTypeface)
|
|
357
|
+
engine.asset.addSource(TextAssetSource(engine, defaultTypeface))
|
|
358
|
+
}
|
|
359
|
+
coroutineContext[Job]?.invokeOnCompletion {
|
|
360
|
+
eventHandler.send(HideLoading)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Default plugin implementation for the *onExport* callback.
|
|
366
|
+
* @param engine The [Engine] that should be used.
|
|
367
|
+
* @param eventHandler The [EditorEventHandler] that should be used.
|
|
368
|
+
* @param mimeType The [MimeType] of the exported artifact.
|
|
369
|
+
* @param thumbnailHeight The height of the generated thumbnail.
|
|
370
|
+
*/
|
|
371
|
+
suspend fun onExport(
|
|
372
|
+
engine: Engine,
|
|
373
|
+
eventHandler: EditorEventHandler,
|
|
374
|
+
mimeType: MimeType,
|
|
375
|
+
thumbnailHeight: Int = 100,
|
|
376
|
+
): EditorResult {
|
|
377
|
+
EditorBuilderDefaults.run {
|
|
378
|
+
eventHandler.send(ShowLoading)
|
|
379
|
+
val blob =
|
|
380
|
+
engine.block.export(
|
|
381
|
+
block = requireNotNull(engine.scene.get()),
|
|
382
|
+
mimeType = mimeType,
|
|
383
|
+
) {
|
|
384
|
+
scene.getPages().forEach {
|
|
385
|
+
block.setScopeEnabled(it, key = "layer/visibility", enabled = true)
|
|
386
|
+
block.setVisible(it, visible = true)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
val tempFile = EditorDefaults.writeToTempFile(blob, mimeType)
|
|
390
|
+
val scene = engine.scene.get()
|
|
391
|
+
val sceneString =
|
|
392
|
+
scene?.let {
|
|
393
|
+
engine.scene.saveToString(it)
|
|
394
|
+
}
|
|
395
|
+
val sceneUri = sceneString?.let { saveScene(it) }
|
|
396
|
+
val firstPage = engine.scene.getPages().first()
|
|
397
|
+
val thumbnail = saveThumbnail(firstPage, engine, thumbnailHeight)
|
|
398
|
+
eventHandler.send(HideLoading)
|
|
399
|
+
return EditorResult(
|
|
400
|
+
scene = sceneUri.toString(),
|
|
401
|
+
artifact = Uri.fromFile(tempFile).toString(),
|
|
402
|
+
thumbnail = thumbnail.toString(),
|
|
403
|
+
)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Default plugin implementation for the *onExport* callback for the video editor.
|
|
409
|
+
* @param engine The [Engine] that should be used.
|
|
410
|
+
* @param eventHandler The [EditorEventHandler] that should be used.
|
|
411
|
+
* @param mimeType The [MimeType] of the exported artifact.
|
|
412
|
+
* @param thumbnailHeight The height of the generated thumbnail.
|
|
413
|
+
*/
|
|
414
|
+
suspend fun onExportVideo(
|
|
415
|
+
engine: Engine,
|
|
416
|
+
eventHandler: EditorEventHandler,
|
|
417
|
+
mimeType: MimeType,
|
|
418
|
+
thumbnailHeight: Int = 100,
|
|
419
|
+
): EditorResult {
|
|
420
|
+
EditorBuilderDefaults.run {
|
|
421
|
+
eventHandler.send(ShowVideoExportProgressEvent(0f))
|
|
422
|
+
|
|
423
|
+
// First create the thumbnail and save the scene
|
|
424
|
+
// in order to finish before the video export sheet is done.
|
|
425
|
+
val page = engine.scene.getCurrentPage() ?: engine.scene.getPages().first()
|
|
426
|
+
val thumbnail = saveThumbnail(page, engine, thumbnailHeight)
|
|
427
|
+
val scene = engine.scene.get()
|
|
428
|
+
val sceneString =
|
|
429
|
+
scene?.let {
|
|
430
|
+
engine.scene.saveToString(it)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
val buffer =
|
|
434
|
+
engine.block.exportVideo(
|
|
435
|
+
block = page,
|
|
436
|
+
timeOffset = 0.0,
|
|
437
|
+
duration = engine.block.getDuration(page),
|
|
438
|
+
mimeType = mimeType,
|
|
439
|
+
progressCallback = { progress ->
|
|
440
|
+
eventHandler.send(
|
|
441
|
+
ShowVideoExportProgressEvent(progress.encodedFrames.toFloat() / progress.totalFrames),
|
|
442
|
+
)
|
|
443
|
+
},
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
val tempFile = EditorDefaults.writeToTempFile(buffer, mimeType)
|
|
447
|
+
eventHandler.send(DismissVideoExportEvent)
|
|
448
|
+
|
|
449
|
+
return EditorResult(
|
|
450
|
+
scene = sceneString,
|
|
451
|
+
artifact = Uri.fromFile(tempFile).toString(),
|
|
452
|
+
thumbnail = thumbnail.toString(),
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private suspend fun saveScene(scene: String): Uri =
|
|
458
|
+
withContext(Dispatchers.IO) {
|
|
459
|
+
val file =
|
|
460
|
+
File
|
|
461
|
+
.createTempFile("cesdk_export_scene_" + UUID.randomUUID().toString(), ".scene")
|
|
462
|
+
file.writeText(scene)
|
|
463
|
+
Uri.fromFile(file)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private suspend fun saveThumbnail(
|
|
467
|
+
id: DesignBlock,
|
|
468
|
+
engine: Engine,
|
|
469
|
+
height: Int,
|
|
470
|
+
): Uri {
|
|
471
|
+
val frames =
|
|
472
|
+
engine.block.generateVideoThumbnailSequence(id, height, timeBegin = 0.0, timeEnd = 0.1, numberOfFrames = 1).toList()
|
|
473
|
+
val firstFrame = frames.first()
|
|
474
|
+
val buffer = firstFrame.imageData
|
|
475
|
+
val tempFile = EditorDefaults.writeToTempFile(buffer, MimeType.PNG)
|
|
476
|
+
return Uri.fromFile(tempFile)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package ly.img.editor.reactnative.module.helper
|
|
2
|
+
|
|
3
|
+
import android.net.Uri
|
|
4
|
+
|
|
5
|
+
/** Helper to use resolve assets. */
|
|
6
|
+
class AssetHelper {
|
|
7
|
+
companion object {
|
|
8
|
+
/**
|
|
9
|
+
* Validates the source of a given asset and returns the Uri if succeeded.
|
|
10
|
+
* @param source The source of the asset.
|
|
11
|
+
* @return The resolved [Uri] as a [String].
|
|
12
|
+
*/
|
|
13
|
+
fun validateAssetSource(source: String): String? {
|
|
14
|
+
fun uriOrNull(uri: Uri?): String? = uri.takeIf { uri?.scheme != null && uri.path != null }?.toString()
|
|
15
|
+
return uriOrNull(Uri.parse(source))
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package ly.img.editor.reactnative.module.helper
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import android.os.Parcelable
|
|
5
|
+
import java.io.Serializable
|
|
6
|
+
|
|
7
|
+
/** A helper to parse Maps for intents */
|
|
8
|
+
class IntentHelper {
|
|
9
|
+
companion object {
|
|
10
|
+
/**
|
|
11
|
+
* Converts a [Map] to a [Bundle].
|
|
12
|
+
* @param map The map to convert.
|
|
13
|
+
* @return The bundle.
|
|
14
|
+
*/
|
|
15
|
+
fun mapToBundle(map: Map<*, *>?): Bundle {
|
|
16
|
+
val bundle = Bundle()
|
|
17
|
+
if (map != null) {
|
|
18
|
+
for ((key, value) in map) {
|
|
19
|
+
if (key is String) {
|
|
20
|
+
when (value) {
|
|
21
|
+
is Int -> bundle.putInt(key, value)
|
|
22
|
+
is Long -> bundle.putLong(key, value)
|
|
23
|
+
is String -> bundle.putString(key, value)
|
|
24
|
+
is Float -> bundle.putFloat(key, value)
|
|
25
|
+
is Double -> bundle.putDouble(key, value)
|
|
26
|
+
is Boolean -> bundle.putBoolean(key, value)
|
|
27
|
+
is Bundle -> bundle.putBundle(key, value)
|
|
28
|
+
is Map<*, *> -> bundle.putBundle(key, mapToBundle(value))
|
|
29
|
+
is Parcelable -> bundle.putParcelable(key, value)
|
|
30
|
+
is Serializable -> bundle.putSerializable(key, value)
|
|
31
|
+
// Add other types as needed
|
|
32
|
+
else -> throw IllegalArgumentException("Unsupported value type: ${value?.javaClass}")
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
throw IllegalArgumentException("Unsupported key type: ${key?.javaClass}")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return bundle
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Converts a [Bundle] to a *Map<String, Any>*
|
|
44
|
+
* @param bundle The [Bundle] to convert.
|
|
45
|
+
* @return The map.
|
|
46
|
+
*/
|
|
47
|
+
fun bundleToMap(bundle: Bundle?): Map<String, Any>? {
|
|
48
|
+
bundle ?: return null
|
|
49
|
+
val map = mutableMapOf<String, Any>()
|
|
50
|
+
for (key in bundle.keySet()) {
|
|
51
|
+
val value = bundle.get(key)
|
|
52
|
+
if (value is Bundle) {
|
|
53
|
+
map[key] = bundleToMap(value)!!
|
|
54
|
+
} else if (value != null) {
|
|
55
|
+
map[key] = value
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return map
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|