react-native-photo-video-editor 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +15 -5
- package/android/src/main/java/com/photovideoeditor/editor/PhotoVideoEditorActivity.kt +43 -7
- package/android/src/main/java/com/photovideoeditor/photo/ui/LayerOverlayView.kt +26 -0
- package/android/src/main/res/drawable/ic_swap_sticker.xml +3 -0
- package/ios/Editor/PhotoVideoEditorViewController.swift +69 -13
- package/ios/Photo/UI/LayerOverlayView.swift +18 -0
- package/ios/PhotoVideoEditor.swift +1 -1
- package/lib/module/index.js +7 -4
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +1 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +19 -15
- package/src/types.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.0
|
|
6
|
+
|
|
7
|
+
- Breaking API change: migrate `initialStickerId: 'brand'` to `initialStickerIds: ['brand']`.
|
|
8
|
+
- Replace `initialStickerId` with `initialStickerIds` and add a top-right control on the selected default sticker to cycle through default stickers on Android and iOS. Swaps preserve transforms and support undo/redo; the button is enabled only for multiple IDs.
|
|
9
|
+
- Place the swap control on the sticker’s top-right corner, above the scale control, so it follows dragging and rotation.
|
|
10
|
+
- Remove dummy sticker data from the example; default stickers are supplied by the caller.
|
|
11
|
+
|
|
5
12
|
## 0.2.2
|
|
6
13
|
|
|
7
14
|
- Add `initialStickerId` to place a matching `stickerAssets` image at the center when opening the photo or video editor on Android and iOS.
|
package/README.md
CHANGED
|
@@ -57,18 +57,28 @@ See [API](docs/api.md), [architecture](docs/architecture.md), [photo editor](doc
|
|
|
57
57
|
For local work, run `yarn`, then `yarn typecheck`, `yarn lint`, `yarn test`, and `yarn prepare`. See [CONTRIBUTING.md](CONTRIBUTING.md). MIT licensed; see [LICENSE](LICENSE).
|
|
58
58
|
|
|
59
59
|
To place a sticker at the center automatically when opening either editor, pass
|
|
60
|
-
`
|
|
60
|
+
`initialStickerIds` containing unique IDs from `stickerAssets`:
|
|
61
61
|
|
|
62
62
|
```ts
|
|
63
63
|
await openPhotoEditor({
|
|
64
64
|
source: { uri: photoUri, type: 'photo' },
|
|
65
|
-
stickerAssets: [
|
|
66
|
-
|
|
65
|
+
stickerAssets: [
|
|
66
|
+
{ id: 'brand', uri: 'https://example.com/brand-sticker.png' },
|
|
67
|
+
{ id: 'alternate', uri: 'https://example.com/alternate.png' },
|
|
68
|
+
],
|
|
69
|
+
initialStickerIds: ['brand', 'alternate'],
|
|
67
70
|
});
|
|
68
71
|
```
|
|
69
72
|
|
|
70
|
-
The sticker starts at the center of the media and can be moved, resized, or
|
|
71
|
-
removed. The
|
|
73
|
+
The first sticker starts at the center of the media and can be moved, resized, or
|
|
74
|
+
removed. The “Switch default sticker” control at the selected default sticker’s top-right
|
|
75
|
+
corner, above its bottom-right scale control, cycles through the IDs in
|
|
76
|
+
order and wraps around, replacing the current default sticker while preserving
|
|
77
|
+
its transforms. Switching is undoable. After deletion, use Undo to restore the sticker and its
|
|
78
|
+
controls. The swap control is disabled for one ID and appears only on the selected
|
|
79
|
+
default sticker. Failed swaps leave the existing sticker in place. The same option works with `openVideoEditor` and `openEditor`, even if
|
|
72
80
|
`features.stickers` hides the sticker picker. Omit it to start without a sticker.
|
|
73
81
|
An unknown or duplicate ID rejects with `E_INVALID_OPTIONS`; an unreadable image
|
|
74
82
|
rejects with `E_SOURCE_UNREADABLE`. HTTPS and local sticker URIs are supported.
|
|
83
|
+
|
|
84
|
+
Migration: replace `initialStickerId: 'brand'` with `initialStickerIds: ['brand']`.
|
|
@@ -188,13 +188,32 @@ class PhotoVideoEditorActivity : Activity() {
|
|
|
188
188
|
if (!isFinishing) insertInitialSticker()
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
private
|
|
192
|
-
val
|
|
191
|
+
private val initialStickerIds: List<String> by lazy {
|
|
192
|
+
val ids = request.optJSONArray("initialStickerIds")
|
|
193
|
+
if (ids == null) emptyList() else (0 until ids.length()).map { ids.optString(it) }.filter { it.isNotBlank() }.distinct()
|
|
194
|
+
}
|
|
195
|
+
private val initialStickerLayerId = java.util.UUID.randomUUID().toString()
|
|
196
|
+
private val initialStickerPaths = mutableMapOf<String, String>()
|
|
197
|
+
private var initialStickerLoading = false
|
|
198
|
+
|
|
199
|
+
private fun currentInitialSticker(): PhotoLayer? =
|
|
200
|
+
(photoSession?.layerStack?.layers ?: videoSession?.layerStack?.layers)?.firstOrNull { it.id == initialStickerLayerId }
|
|
201
|
+
|
|
202
|
+
private fun switchInitialSticker() {
|
|
203
|
+
if (initialStickerLoading || initialStickerIds.size < 2) return
|
|
204
|
+
val currentIndex = initialStickerIds.indexOf(currentInitialSticker()?.stickerId)
|
|
205
|
+
insertInitialSticker((currentIndex + 1) % initialStickerIds.size)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private fun insertInitialSticker(index: Int = 0) {
|
|
209
|
+
if (initialStickerLoading) return
|
|
210
|
+
val id = initialStickerIds.getOrNull(index) ?: return
|
|
193
211
|
val asset = runtimeStickerAssets().firstOrNull { it.id == id } ?: return
|
|
212
|
+
initialStickerLoading = true
|
|
194
213
|
window.setFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
|
|
195
214
|
Thread {
|
|
196
215
|
val path = try {
|
|
197
|
-
val localPath = if (asset.uri.startsWith("https://", ignoreCase = true)) {
|
|
216
|
+
val localPath = initialStickerPaths[id] ?: if (asset.uri.startsWith("https://", ignoreCase = true)) {
|
|
198
217
|
val output = File.createTempFile("pve_initial_sticker_", ".img", cacheDir)
|
|
199
218
|
val connection = java.net.URL(asset.uri).openConnection().apply {
|
|
200
219
|
connectTimeout = 10_000
|
|
@@ -210,20 +229,31 @@ class PhotoVideoEditorActivity : Activity() {
|
|
|
210
229
|
}
|
|
211
230
|
} catch (_: Exception) { null }
|
|
212
231
|
runOnUiThread {
|
|
232
|
+
initialStickerLoading = false
|
|
213
233
|
window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
|
|
214
234
|
if (isFinishing || isDestroyed) return@runOnUiThread
|
|
215
235
|
if (path == null) {
|
|
216
|
-
finishWithError("The initial sticker could not be loaded.", "E_SOURCE_UNREADABLE")
|
|
236
|
+
if (initialStickerPaths.isEmpty()) finishWithError("The initial sticker could not be loaded.", "E_SOURCE_UNREADABLE")
|
|
237
|
+
else Toast.makeText(this, "Could not load this sticker. Try again.", Toast.LENGTH_SHORT).show()
|
|
217
238
|
return@runOnUiThread
|
|
218
239
|
}
|
|
240
|
+
initialStickerPaths[id] = path
|
|
219
241
|
window.decorView.post {
|
|
220
242
|
if (isFinishing || isDestroyed) return@post
|
|
221
|
-
val
|
|
243
|
+
val uri = Uri.fromFile(File(path)).toString()
|
|
244
|
+
val fresh = newSticker(stickerId = id, stickerUri = uri)
|
|
245
|
+
val layer = currentInitialSticker()?.copy(stickerId = id, stickerUri = uri, overlayAspectRatio = fresh.overlayAspectRatio)
|
|
246
|
+
?: fresh.copy(id = initialStickerLayerId)
|
|
247
|
+
val replace: (List<PhotoLayer>) -> List<PhotoLayer> = { layers ->
|
|
248
|
+
if (layers.any { it.id == initialStickerLayerId }) layers.map { if (it.id == initialStickerLayerId) layer else it }
|
|
249
|
+
else layers + layer
|
|
250
|
+
}
|
|
222
251
|
if (mediaType == "photo") {
|
|
223
|
-
photoSession?.layerStack?.commit
|
|
252
|
+
photoSession?.layerStack?.commit(replace)
|
|
253
|
+
onLayerStackChanged()
|
|
224
254
|
selectLayer(layer.id)
|
|
225
255
|
} else videoSession?.let { session ->
|
|
226
|
-
session.layerStack.commit
|
|
256
|
+
session.layerStack.commit(replace)
|
|
227
257
|
onVideoLayerStackChanged()
|
|
228
258
|
selectVideoLayer(layer.id, session)
|
|
229
259
|
}
|
|
@@ -320,6 +350,9 @@ class PhotoVideoEditorActivity : Activity() {
|
|
|
320
350
|
previewContainer.addView(overlay, FrameLayout.LayoutParams(MATCH, MATCH))
|
|
321
351
|
|
|
322
352
|
val layers = LayerOverlayView(this)
|
|
353
|
+
layers.swappableLayerId = initialStickerLayerId
|
|
354
|
+
layers.stickerSwapEnabled = initialStickerIds.size > 1
|
|
355
|
+
layers.onStickerSwap = { switchInitialSticker() }
|
|
323
356
|
layerOverlay = layers
|
|
324
357
|
previewContainer.addView(layers, FrameLayout.LayoutParams(MATCH, MATCH))
|
|
325
358
|
|
|
@@ -1501,6 +1534,9 @@ class PhotoVideoEditorActivity : Activity() {
|
|
|
1501
1534
|
if (videoCropMode) videoCropOverlay.restore(videoMediaBounds(session, playerView, false), session.state.crop)
|
|
1502
1535
|
}
|
|
1503
1536
|
val layerOverlay = LayerOverlayView(this)
|
|
1537
|
+
layerOverlay.swappableLayerId = initialStickerLayerId
|
|
1538
|
+
layerOverlay.stickerSwapEnabled = initialStickerIds.size > 1
|
|
1539
|
+
layerOverlay.onStickerSwap = { switchInitialSticker() }
|
|
1504
1540
|
videoLayerOverlay = layerOverlay
|
|
1505
1541
|
preview.addView(layerOverlay, FrameLayout.LayoutParams(MATCH, MATCH))
|
|
1506
1542
|
layerOverlay.onLayerDelete = { id ->
|
|
@@ -28,6 +28,11 @@ class LayerOverlayView(context: Context) : View(context) {
|
|
|
28
28
|
set(value) { field = value; invalidate() }
|
|
29
29
|
var selectedLayerId: String? = null
|
|
30
30
|
set(value) { field = value; invalidate() }
|
|
31
|
+
var swappableLayerId: String? = null
|
|
32
|
+
set(value) { field = value; invalidate() }
|
|
33
|
+
var stickerSwapEnabled = false
|
|
34
|
+
set(value) { field = value; invalidate() }
|
|
35
|
+
var onStickerSwap: (() -> Unit)? = null
|
|
31
36
|
var onLayerDelete: ((String) -> Unit)? = null
|
|
32
37
|
var onLayerTapped: ((String?) -> Unit)? = null
|
|
33
38
|
var onLayerDoubleTapped: ((String) -> Unit)? = null
|
|
@@ -71,6 +76,10 @@ class LayerOverlayView(context: Context) : View(context) {
|
|
|
71
76
|
}
|
|
72
77
|
drawControl(canvas, -half.first, -half.second, R.drawable.ic_close, Color.rgb(255, 100, 115))
|
|
73
78
|
drawControl(canvas, half.first, half.second, R.drawable.ic_fullscreen, Color.rgb(167, 139, 250))
|
|
79
|
+
if (selected.type == LayerType.STICKER && selected.id == swappableLayerId) {
|
|
80
|
+
drawControl(canvas, half.first, -half.second, R.drawable.ic_swap_sticker,
|
|
81
|
+
if (stickerSwapEnabled && !selected.locked) Color.rgb(167, 139, 250) else Color.GRAY)
|
|
82
|
+
}
|
|
74
83
|
canvas.restore()
|
|
75
84
|
}
|
|
76
85
|
|
|
@@ -92,6 +101,17 @@ class LayerOverlayView(context: Context) : View(context) {
|
|
|
92
101
|
if (selected != null && !selected.locked) {
|
|
93
102
|
val handle = handlePoint(selected)
|
|
94
103
|
val center = centerOf(selected)
|
|
104
|
+
if (selected.type == LayerType.STICKER && selected.id == swappableLayerId) {
|
|
105
|
+
val swap = swapPoint(selected)
|
|
106
|
+
val swapDistance = hypot(event.x - swap.first, event.y - swap.second)
|
|
107
|
+
val deleteDistance = hypot(event.x - (2 * center.first - handle.first), event.y - (2 * center.second - handle.second))
|
|
108
|
+
val scaleDistance = hypot(event.x - handle.first, event.y - handle.second)
|
|
109
|
+
if (swapDistance <= handleTouchRadius && swapDistance <= deleteDistance && swapDistance <= scaleDistance) {
|
|
110
|
+
resetGesture()
|
|
111
|
+
if (stickerSwapEnabled) onStickerSwap?.invoke()
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
}
|
|
95
115
|
if (hypot(event.x - (2 * center.first - handle.first), event.y - (2 * center.second - handle.second)) <= handleTouchRadius) {
|
|
96
116
|
onLayerDelete?.invoke(selected.id); resetGesture(); return true
|
|
97
117
|
}
|
|
@@ -225,6 +245,12 @@ class LayerOverlayView(context: Context) : View(context) {
|
|
|
225
245
|
return Pair(center.first + (half.first * cos(radians) - half.second * sin(radians)).toFloat(), center.second + (half.first * sin(radians) + half.second * cos(radians)).toFloat())
|
|
226
246
|
}
|
|
227
247
|
|
|
248
|
+
private fun swapPoint(layer: PhotoLayer): Pair<Float, Float> {
|
|
249
|
+
val center = centerOf(layer); val half = selectionHalfExtents(layer)
|
|
250
|
+
val radians = Math.toRadians(layer.rotationDegrees.toDouble())
|
|
251
|
+
return Pair(center.first + (half.first * cos(radians) + half.second * sin(radians)).toFloat(), center.second + (half.first * sin(radians) - half.second * cos(radians)).toFloat())
|
|
252
|
+
}
|
|
253
|
+
|
|
228
254
|
private fun isHandleHit(layer: PhotoLayer, x: Float, y: Float): Boolean {
|
|
229
255
|
val handle = handlePoint(layer)
|
|
230
256
|
return hypot(x - handle.first, y - handle.second) <= handleTouchRadius
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
|
2
|
+
<path android:fillColor="#FFFFFFFF" android:pathData="M7,7h11l-3,-3 1.4,-1.4L22,8l-5.6,5.4L15,12l3,-3H7zM17,17H6l3,3 -1.4,1.4L2,16l5.6,-5.4L9,12l-3,3h11z" />
|
|
3
|
+
</vector>
|
|
@@ -15,7 +15,10 @@ final class PhotoVideoEditorViewController: UIViewController {
|
|
|
15
15
|
private let features: [String: Any]
|
|
16
16
|
private let theme: [String: Any]
|
|
17
17
|
private let exportOptions: [String: Any]
|
|
18
|
-
private let
|
|
18
|
+
private let initialStickerIds: [String]
|
|
19
|
+
private let initialStickerLayerID = UUID().uuidString
|
|
20
|
+
private var initialStickerImages: [String: (URL, UIImage)] = [:]
|
|
21
|
+
private var initialStickerLoading = false
|
|
19
22
|
private let stickerAssets: [[String: Any]]
|
|
20
23
|
var completion: ((PhotoVideoEditorOutcome) -> Void)?
|
|
21
24
|
|
|
@@ -119,7 +122,7 @@ final class PhotoVideoEditorViewController: UIViewController {
|
|
|
119
122
|
theme: [String: Any] = [:],
|
|
120
123
|
exportOptions: [String: Any] = [:],
|
|
121
124
|
stickerAssets: [[String: Any]] = [],
|
|
122
|
-
|
|
125
|
+
initialStickerIds: [String] = []
|
|
123
126
|
) {
|
|
124
127
|
self.uri = uri
|
|
125
128
|
self.mediaType = mediaType
|
|
@@ -127,7 +130,7 @@ final class PhotoVideoEditorViewController: UIViewController {
|
|
|
127
130
|
self.theme = theme
|
|
128
131
|
self.exportOptions = exportOptions
|
|
129
132
|
self.stickerAssets = stickerAssets
|
|
130
|
-
self.
|
|
133
|
+
self.initialStickerIds = initialStickerIds
|
|
131
134
|
super.init(nibName: nil, bundle: nil)
|
|
132
135
|
modalPresentationStyle = .fullScreen
|
|
133
136
|
}
|
|
@@ -160,21 +163,68 @@ final class PhotoVideoEditorViewController: UIViewController {
|
|
|
160
163
|
insertInitialSticker()
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
private func
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
private func currentInitialSticker() -> PhotoLayer? {
|
|
167
|
+
(photoSession?.layerStack.layers ?? videoSession?.layerStack.layers ?? []).first { $0.id == initialStickerLayerID }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private func switchInitialSticker() {
|
|
171
|
+
guard !initialStickerLoading, initialStickerIds.count > 1 else { return }
|
|
172
|
+
let currentID = currentInitialSticker()?.stickerId
|
|
173
|
+
let currentIndex = currentID.flatMap { initialStickerIds.firstIndex(of: $0) } ?? -1
|
|
174
|
+
insertInitialSticker(index: (currentIndex + 1) % initialStickerIds.count)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private func insertInitialSticker(index: Int = 0) {
|
|
178
|
+
guard !initialStickerLoading, initialStickerIds.indices.contains(index) else { return }
|
|
179
|
+
let id = initialStickerIds[index]
|
|
180
|
+
guard let asset = consumerStickerAssets().first(where: { $0.id == id }) else { return }
|
|
181
|
+
initialStickerLoading = true
|
|
166
182
|
view.isUserInteractionEnabled = false
|
|
167
|
-
let
|
|
168
|
-
loader.loadRuntimeSticker(.init(id: id, uri: asset.uri)) { [weak self, loader] url, image in
|
|
169
|
-
_ = loader // Keep the loader alive until its download finishes.
|
|
183
|
+
let apply: (URL?, UIImage?) -> Void = { [weak self] url, image in
|
|
170
184
|
guard let self else { return }
|
|
185
|
+
self.initialStickerLoading = false
|
|
171
186
|
self.view.isUserInteractionEnabled = true
|
|
172
187
|
guard let url, let image else {
|
|
173
|
-
self.
|
|
188
|
+
if self.initialStickerImages.isEmpty {
|
|
189
|
+
self.completion?(.failure(code: "E_SOURCE_UNREADABLE", message: "The initial sticker could not be loaded."))
|
|
190
|
+
} else {
|
|
191
|
+
let alert = UIAlertController(title: "Couldn't load sticker", message: "Please try again.", preferredStyle: .alert)
|
|
192
|
+
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
|
193
|
+
self.present(alert, animated: true)
|
|
194
|
+
}
|
|
174
195
|
return
|
|
175
196
|
}
|
|
197
|
+
self.initialStickerImages[id] = (url, image)
|
|
176
198
|
self.view.layoutIfNeeded()
|
|
177
|
-
|
|
199
|
+
var layer = self.currentInitialSticker() ?? self.newSticker()
|
|
200
|
+
layer.id = self.initialStickerLayerID
|
|
201
|
+
layer.stickerId = id
|
|
202
|
+
layer.stickerUri = url.absoluteString
|
|
203
|
+
layer.overlayAspectRatio = max(image.size.width, 1) / max(image.size.height, 1)
|
|
204
|
+
let replace: ([PhotoLayer]) -> [PhotoLayer] = { layers in
|
|
205
|
+
if layers.contains(where: { $0.id == self.initialStickerLayerID }) {
|
|
206
|
+
return layers.map { $0.id == self.initialStickerLayerID ? layer : $0 }
|
|
207
|
+
}
|
|
208
|
+
return layers + [layer]
|
|
209
|
+
}
|
|
210
|
+
if let session = self.photoSession {
|
|
211
|
+
session.layerStack.commit(replace)
|
|
212
|
+
self.onLayerStackChanged()
|
|
213
|
+
self.selectLayer(layer.id)
|
|
214
|
+
} else if let session = self.videoSession {
|
|
215
|
+
session.layerStack.commit(replace)
|
|
216
|
+
self.onVideoLayerStackChanged()
|
|
217
|
+
self.selectVideoLayer(layer.id, session: session)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if let cached = initialStickerImages[id] {
|
|
221
|
+
apply(cached.0, cached.1)
|
|
222
|
+
} else {
|
|
223
|
+
let loader = OnlineStickerSheetViewController()
|
|
224
|
+
loader.loadRuntimeSticker(.init(id: id, uri: asset.uri)) { [loader] url, image in
|
|
225
|
+
_ = loader // Keep the loader alive until its download finishes.
|
|
226
|
+
apply(url, image)
|
|
227
|
+
}
|
|
178
228
|
}
|
|
179
229
|
}
|
|
180
230
|
|
|
@@ -233,7 +283,10 @@ final class PhotoVideoEditorViewController: UIViewController {
|
|
|
233
283
|
overlay.bottomAnchor.constraint(equalTo: preview.bottomAnchor),
|
|
234
284
|
])
|
|
235
285
|
|
|
236
|
-
let layers = LayerOverlayView()
|
|
286
|
+
let layers = LayerOverlayView(frame: .zero)
|
|
287
|
+
layers.swappableLayerID = initialStickerLayerID
|
|
288
|
+
layers.stickerSwapEnabled = initialStickerIds.count > 1
|
|
289
|
+
layers.onStickerSwap = { [weak self] in self?.switchInitialSticker() }
|
|
237
290
|
layerOverlay = layers
|
|
238
291
|
preview.addSubview(layers)
|
|
239
292
|
layers.translatesAutoresizingMaskIntoConstraints = false
|
|
@@ -1568,7 +1621,10 @@ final class PhotoVideoEditorViewController: UIViewController {
|
|
|
1568
1621
|
let scale = bounds.width/max(fitted.width,0.001)
|
|
1569
1622
|
playerView.transform = CGAffineTransform(translationX:bounds.midX-fitted.midX,y:bounds.midY-fitted.midY).scaledBy(x:scale,y:scale)
|
|
1570
1623
|
}
|
|
1571
|
-
let videoLayers = LayerOverlayView()
|
|
1624
|
+
let videoLayers = LayerOverlayView(frame: .zero)
|
|
1625
|
+
videoLayers.swappableLayerID = initialStickerLayerID
|
|
1626
|
+
videoLayers.stickerSwapEnabled = initialStickerIds.count > 1
|
|
1627
|
+
videoLayers.onStickerSwap = { [weak self] in self?.switchInitialSticker() }
|
|
1572
1628
|
videoLayerOverlay = videoLayers
|
|
1573
1629
|
preview.addSubview(videoLayers)
|
|
1574
1630
|
videoLayers.translatesAutoresizingMaskIntoConstraints = false
|
|
@@ -8,6 +8,9 @@ final class LayerOverlayView: UIView {
|
|
|
8
8
|
var maxScale: CGFloat = 8
|
|
9
9
|
var layers: [PhotoLayer] = [] { didSet { setNeedsDisplay() } }
|
|
10
10
|
var selectedLayerID: String? { didSet { setNeedsDisplay() } }
|
|
11
|
+
var swappableLayerID: String? { didSet { setNeedsDisplay() } }
|
|
12
|
+
var stickerSwapEnabled = false { didSet { setNeedsDisplay() } }
|
|
13
|
+
var onStickerSwap: (() -> Void)?
|
|
11
14
|
var onLayerDelete: ((String) -> Void)?
|
|
12
15
|
var onLayerTapped: ((String?) -> Void)?
|
|
13
16
|
var onLayerDoubleTapped: ((String) -> Void)?
|
|
@@ -52,6 +55,9 @@ final class LayerOverlayView: UIView {
|
|
|
52
55
|
}
|
|
53
56
|
drawControl(context, point: CGPoint(x: -half.width, y: -half.height), symbol: "xmark", tint: .systemRed)
|
|
54
57
|
drawControl(context, point: CGPoint(x: half.width, y: half.height), symbol: "arrow.up.left.and.arrow.down.right", tint: DesignTokens.primary)
|
|
58
|
+
if layer.type == .sticker && layer.id == swappableLayerID {
|
|
59
|
+
drawControl(context, point: CGPoint(x: half.width, y: -half.height), symbol: "arrow.left.arrow.right", tint: stickerSwapEnabled && !layer.locked ? DesignTokens.primary : .gray)
|
|
60
|
+
}
|
|
55
61
|
context.restoreGState()
|
|
56
62
|
}
|
|
57
63
|
|
|
@@ -80,6 +86,14 @@ final class LayerOverlayView: UIView {
|
|
|
80
86
|
if let selected, !selected.locked {
|
|
81
87
|
let center = center(of: selected); let handle = handlePoint(selected)
|
|
82
88
|
let deletePoint = CGPoint(x: center.x * 2 - handle.x, y: center.y * 2 - handle.y)
|
|
89
|
+
if selected.type == .sticker && selected.id == swappableLayerID {
|
|
90
|
+
let swapDistance = distance(swapPoint(selected), point)
|
|
91
|
+
if swapDistance <= handleTouchRadius && swapDistance <= distance(deletePoint, point) && swapDistance <= distance(handle, point) {
|
|
92
|
+
resetGesture()
|
|
93
|
+
if stickerSwapEnabled { onStickerSwap?() }
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
}
|
|
83
97
|
if distance(deletePoint, point) <= handleTouchRadius { onLayerDelete?(selected.id); resetGesture(); return }
|
|
84
98
|
}
|
|
85
99
|
let handleHit = selected.flatMap { !$0.locked && isHandleHit($0, point) ? $0 : nil }
|
|
@@ -178,6 +192,10 @@ final class LayerOverlayView: UIView {
|
|
|
178
192
|
let center = center(of: layer); let half = selectionHalfExtents(layer); let radians = layer.rotationDegrees * .pi / 180
|
|
179
193
|
return CGPoint(x: center.x + half.width * cos(radians) - half.height * sin(radians), y: center.y + half.width * sin(radians) + half.height * cos(radians))
|
|
180
194
|
}
|
|
195
|
+
private func swapPoint(_ layer: PhotoLayer) -> CGPoint {
|
|
196
|
+
let center = center(of: layer); let half = selectionHalfExtents(layer); let radians = layer.rotationDegrees * .pi / 180
|
|
197
|
+
return CGPoint(x: center.x + half.width * cos(radians) + half.height * sin(radians), y: center.y + half.width * sin(radians) - half.height * cos(radians))
|
|
198
|
+
}
|
|
181
199
|
private func isHandleHit(_ layer: PhotoLayer, _ point: CGPoint) -> Bool { distance(handlePoint(layer), point) <= handleTouchRadius }
|
|
182
200
|
private func hitTest(_ point: CGPoint) -> PhotoLayer? {
|
|
183
201
|
for layer in layers.reversed() where layer.visible {
|
|
@@ -31,7 +31,7 @@ public class PhotoVideoEditorSwift: NSObject {
|
|
|
31
31
|
theme: payload["theme"] as? [String: Any] ?? [:],
|
|
32
32
|
exportOptions: payload["export"] as? [String: Any] ?? [:],
|
|
33
33
|
stickerAssets: payload["stickerAssets"] as? [[String: Any]] ?? [],
|
|
34
|
-
|
|
34
|
+
initialStickerIds: payload["initialStickerIds"] as? [String] ?? []
|
|
35
35
|
)
|
|
36
36
|
controller.completion = { [weak self, weak controller] outcome in
|
|
37
37
|
guard let self else { return }
|
package/lib/module/index.js
CHANGED
|
@@ -39,10 +39,13 @@ function normalizeOptions(input) {
|
|
|
39
39
|
positive(output.frameRate, 'export.frameRate');
|
|
40
40
|
if (input.source.type === 'photo' && output.videoFormat !== undefined) invalid('videoFormat is only valid for video sources.');
|
|
41
41
|
if (input.source.type === 'video' && output.imageFormat !== undefined) invalid('imageFormat is only valid for photo sources.');
|
|
42
|
-
if (input.
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
if (input.initialStickerIds !== undefined) {
|
|
43
|
+
if (!Array.isArray(input.initialStickerIds) || input.initialStickerIds.some(id => typeof id !== 'string' || !id.trim())) invalid('initialStickerIds must be an array of non-empty strings.');
|
|
44
|
+
if (new Set(input.initialStickerIds).size !== input.initialStickerIds.length) invalid('initialStickerIds must contain unique IDs.');
|
|
45
|
+
for (const id of input.initialStickerIds) {
|
|
46
|
+
const matches = Array.isArray(input.stickerAssets) ? input.stickerAssets.filter(asset => asset?.id === id) : [];
|
|
47
|
+
if (matches.length !== 1 || typeof matches[0]?.uri !== 'string' || !matches[0].uri.trim()) invalid('Each initialStickerIds entry must match exactly one stickerAssets entry with a non-empty uri.');
|
|
48
|
+
}
|
|
46
49
|
}
|
|
47
50
|
const features = {
|
|
48
51
|
...defaultFeatures,
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativePhotoVideoEditor","PhotoVideoEditorError","normalizeEditorError","addEditorEventListener","addExportProgressListener","EDITOR_PROJECT_SCHEMA_VERSION","mediaTypes","Set","qualities","imageFormats","videoFormats","defaultFeatures","crop","rotate","trim","filters","text","stickers","draw","overlays","invalid","message","positive","value","name","undefined","Number","isFinite","normalizeOptions","input","source","uri","has","type","output","export","quality","imageFormat","videoFormat","maxWidth","maxHeight","videoBitrate","frameRate","
|
|
1
|
+
{"version":3,"names":["NativePhotoVideoEditor","PhotoVideoEditorError","normalizeEditorError","addEditorEventListener","addExportProgressListener","EDITOR_PROJECT_SCHEMA_VERSION","mediaTypes","Set","qualities","imageFormats","videoFormats","defaultFeatures","crop","rotate","trim","filters","text","stickers","draw","overlays","invalid","message","positive","value","name","undefined","Number","isFinite","normalizeOptions","input","source","uri","has","type","output","export","quality","imageFormat","videoFormat","maxWidth","maxHeight","videoBitrate","frameRate","initialStickerIds","Array","isArray","some","id","size","length","matches","stickerAssets","filter","asset","features","preserveMetadata","saveToGallery","openEditor","options","JSON","parse","stringify","error","openPhotoEditor","openVideoEditor","cancelExport","jobId","isAvailable"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAIZ,kBAAkB,EAElB,kBAAkB,EACnB,MAAM,YAAS,CAAC;AAEjB,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,aAAU,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAU,CAAC;AACjD,mBAAmB,YAAS,CAAC;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAW,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,cAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAIZ,kBAAkB,EAElB,kBAAkB,EACnB,MAAM,YAAS,CAAC;AAEjB,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,aAAU,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAU,CAAC;AACjD,mBAAmB,YAAS,CAAC;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAW,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,cAAW,CAAC;AA8F/C,mEAAmE;AACnE,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAUvB;AACD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;AACD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;AACD,0FAA0F;AAC1F,wBAAsB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMnE;AACD,4EAA4E;AAC5E,wBAAgB,WAAW,IAAI,OAAO,CAErC"}
|
|
@@ -62,7 +62,7 @@ export interface EditorOptions {
|
|
|
62
62
|
/** Native UI theme. */ theme?: EditorTheme;
|
|
63
63
|
/** Export preferences. */ export?: ExportOptions;
|
|
64
64
|
/** Requests saving to the gallery. Currently ignored by the placeholder. */ saveToGallery?: boolean;
|
|
65
|
-
/**
|
|
65
|
+
/** Ordered, unique IDs in stickerAssets. The first starts centered; the sticker’s top-right control cycles through the rest. */ initialStickerIds?: string[];
|
|
66
66
|
/** Consumer-provided stickers offered by the Stickers tool, in addition to the small built-in set and end-user uploads. */ stickerAssets?: StickerAsset[];
|
|
67
67
|
/** Consumer-provided fonts selectable from the Text tool. */ fonts?: FontAsset[];
|
|
68
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAC1C,uCAAuC;AACvC,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AACnE,wCAAwC;AACxC,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AACxD,wCAAwC;AACxC,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,KAAK,CAAC;AAC9C,sDAAsD;AACtD,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,WAAW,GACX,SAAS,GACT,WAAW,GACX,WAAW,GACX,WAAW,GACX,QAAQ,CAAC;AAEb,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,8BAA8B,CAAC,GAAG,EAAE,MAAM,CAAC;IAC3C,kBAAkB,CAAC,IAAI,EAAE,SAAS,CAAC;IACnC,+BAA+B,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClD,iCAAiC,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrD;AACD,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,wBAAwB,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxC,wBAAwB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1C,8BAA8B,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAC9C,uBAAuB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC1C,6BAA6B,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAC7C,yEAAyE,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC7F,0EAA0E,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAC1F,oDAAoD,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACzE;AACD,+CAA+C;AAC/C,MAAM,WAAW,WAAW;IAC1B,+BAA+B,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtD,+BAA+B,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzD,qBAAqB,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC5C,6BAA6B,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IACjD,gCAAgC,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACpE;AACD,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,sBAAsB,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IAC/C,+CAA+C,CAAC,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChF,+CAA+C,CAAC,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChF,wDAAwD,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3E,yDAAyD,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC7E,0DAA0D,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IACjF,+DAA+D,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnF,0DAA0D,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACvF;AACD,4FAA4F;AAC5F,MAAM,WAAW,SAAS;IACxB,mEAAmE,CAAC,MAAM,EAAE,MAAM,CAAC;IACnF,wDAAwD,CAAC,GAAG,EAAE,MAAM,CAAC;CACtE;AACD,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,wEAAwE,CAAC,EAAE,EAAE,MAAM,CAAC;IACpF,8GAA8G,CAAC,GAAG,EAAE,MAAM,CAAC;CAC5H;AACD,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B,4BAA4B,CAAC,MAAM,EAAE,YAAY,CAAC;IAClD,qBAAqB,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAChD,uBAAuB,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC5C,0BAA0B,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAClD,4EAA4E,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACrG
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -72,25 +72,29 @@ function normalizeOptions(input: EditorOptions): EditorOptions {
|
|
|
72
72
|
invalid('videoFormat is only valid for video sources.');
|
|
73
73
|
if (input.source.type === 'video' && output.imageFormat !== undefined)
|
|
74
74
|
invalid('imageFormat is only valid for photo sources.');
|
|
75
|
-
if (input.
|
|
75
|
+
if (input.initialStickerIds !== undefined) {
|
|
76
76
|
if (
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
!Array.isArray(input.initialStickerIds) ||
|
|
78
|
+
input.initialStickerIds.some((id) => typeof id !== 'string' || !id.trim())
|
|
79
79
|
)
|
|
80
|
-
invalid('
|
|
81
|
-
const matches = Array.isArray(input.stickerAssets)
|
|
82
|
-
? input.stickerAssets.filter(
|
|
83
|
-
(asset) => asset?.id === input.initialStickerId
|
|
84
|
-
)
|
|
85
|
-
: [];
|
|
80
|
+
invalid('initialStickerIds must be an array of non-empty strings.');
|
|
86
81
|
if (
|
|
87
|
-
|
|
88
|
-
typeof matches[0]?.uri !== 'string' ||
|
|
89
|
-
!matches[0].uri.trim()
|
|
82
|
+
new Set(input.initialStickerIds).size !== input.initialStickerIds.length
|
|
90
83
|
)
|
|
91
|
-
invalid(
|
|
92
|
-
|
|
93
|
-
)
|
|
84
|
+
invalid('initialStickerIds must contain unique IDs.');
|
|
85
|
+
for (const id of input.initialStickerIds) {
|
|
86
|
+
const matches = Array.isArray(input.stickerAssets)
|
|
87
|
+
? input.stickerAssets.filter((asset) => asset?.id === id)
|
|
88
|
+
: [];
|
|
89
|
+
if (
|
|
90
|
+
matches.length !== 1 ||
|
|
91
|
+
typeof matches[0]?.uri !== 'string' ||
|
|
92
|
+
!matches[0].uri.trim()
|
|
93
|
+
)
|
|
94
|
+
invalid(
|
|
95
|
+
'Each initialStickerIds entry must match exactly one stickerAssets entry with a non-empty uri.'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
94
98
|
}
|
|
95
99
|
const features = { ...defaultFeatures, ...input.features };
|
|
96
100
|
if (input.source.type === 'photo') {
|
package/src/types.ts
CHANGED
|
@@ -70,7 +70,7 @@ export interface EditorOptions {
|
|
|
70
70
|
/** Native UI theme. */ theme?: EditorTheme;
|
|
71
71
|
/** Export preferences. */ export?: ExportOptions;
|
|
72
72
|
/** Requests saving to the gallery. Currently ignored by the placeholder. */ saveToGallery?: boolean;
|
|
73
|
-
/**
|
|
73
|
+
/** Ordered, unique IDs in stickerAssets. The first starts centered; the sticker’s top-right control cycles through the rest. */ initialStickerIds?: string[];
|
|
74
74
|
/** Consumer-provided stickers offered by the Stickers tool, in addition to the small built-in set and end-user uploads. */ stickerAssets?: StickerAsset[];
|
|
75
75
|
/** Consumer-provided fonts selectable from the Text tool. */ fonts?: FontAsset[];
|
|
76
76
|
}
|