react-native-photo-video-editor 0.2.0 → 0.2.2

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 CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.2
6
+
7
+ - Add `initialStickerId` to place a matching `stickerAssets` image at the center when opening the photo or video editor on Android and iOS.
8
+ - Validate initial sticker IDs before opening the editor and report unreadable initial images.
9
+ - Support base64 PNG data URIs for locally embedded sticker images.
10
+ - Keep initial stickers optional: the editor opens without a sticker unless one is supplied.
11
+
12
+ ## 0.2.1
13
+
14
+ - Fix an iOS build failure in `TextEditorSheet`: its private stored property `editing` collided with `UIViewController.isEditing`, which is exported to Objective-C as `editing`, so the compiler treated it as an invalid override ("cannot override with a stored property" / "overriding property must be as accessible as its enclosing type"). The property is now `isEditingExisting`; the `editing:` initialiser label is unchanged, so call sites are unaffected.
15
+ - Fix three more iOS build failures in `PhotoVideoEditorViewController`:
16
+ - `ZoomableImageView()` had no matching initialiser. The class overrides `init(frame:)`, so it does not inherit `UIView`'s no-argument initialiser; the call is now `ZoomableImageView(frame: .zero)`. This also cleared the cascading "cannot infer type of closure parameter 'bounds'" error on the `onBoundsChanged` assignment.
17
+ - Two layer-transform handlers used an immediately-applied closure inside a ternary (`{ var layer = $0; ... }()`). The `$0` inside bound to the inner closure's own parameter rather than the enclosing `map`'s element, making it a one-argument closure invoked with none. Both are rewritten as an explicit `map { current in ... }` with a `guard`.
18
+ - Fix an iOS build failure in `PhotoExporter`: `(exportOptions?["maxWidth"] as? NSNumber)?.doubleValue.map { ... }` applied `map` to the non-optional `Double` inside the optional chain rather than to the optional itself. `map` now runs on the optional `NSNumber`, keeping the `CGFloat?` type that `resize(_:maxWidth:maxHeight:)` expects.
19
+ - Fix an iOS build failure in `VideoEditSession`: inside `private extension Int64`, the bare `min`/`max` in `clamped(to:)` resolved to the static properties `Int64.min`/`Int64.max` instead of the global functions. They are now qualified as `Swift.min`/`Swift.max`.
20
+ - Fix an iOS build failure in `PhotoVideoEditor.mm`: the TurboModule class subclassed the Swift `PhotoVideoEditorSwift`, which Objective-C cannot do — Swift emits every class into the generated header with `objc_subclassing_restricted`. The TurboModule now holds a `PhotoVideoEditorSwift` instance and forwards `openEditor`, `cancelExport` and `isAvailable` to it.
21
+
5
22
  ## 0.2.0
6
23
 
7
24
  - Fix video export placing text/sticker overlays at the wrong size and position on Android. Media3 composites an overlay texture 1:1 in pixels rather than stretching it to fill the frame, so the overlay bitmap's resolution cap shrank every layer toward the frame centre on sources above the cap (4K/1440p); 1080p and below were unaffected. The overlay canvas is now scaled back to the full frame via `OverlaySettings`, making preview and export match at any resolution. iOS was never affected (its `CALayer` scales to fit).
package/README.md CHANGED
@@ -55,3 +55,20 @@ try {
55
55
  See [API](docs/api.md), [architecture](docs/architecture.md), [photo editor](docs/photo-editor.md), [video editor](docs/video-editor.md), and [roadmap](docs/roadmap.md). Known limitations are non-functional tool placeholders, no transformed export, local-URI-only input, and no verified legacy-architecture support.
56
56
 
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
+
59
+ To place a sticker at the center automatically when opening either editor, pass
60
+ `initialStickerId` matching exactly one entry in `stickerAssets`:
61
+
62
+ ```ts
63
+ await openPhotoEditor({
64
+ source: { uri: photoUri, type: 'photo' },
65
+ stickerAssets: [{ id: 'brand', uri: 'https://example.com/brand-sticker.png' }],
66
+ initialStickerId: 'brand',
67
+ });
68
+ ```
69
+
70
+ The sticker starts at the center of the media and can be moved, resized, or
71
+ removed. The same option works with `openVideoEditor` and `openEditor`, even if
72
+ `features.stickers` hides the sticker picker. Omit it to start without a sticker.
73
+ An unknown or duplicate ID rejects with `E_INVALID_OPTIONS`; an unreadable image
74
+ rejects with `E_SOURCE_UNREADABLE`. HTTPS and local sticker URIs are supported.
@@ -185,6 +185,51 @@ class PhotoVideoEditorActivity : Activity() {
185
185
  }
186
186
  applyStatusBarStyle()
187
187
  setContentView(createEditorView())
188
+ if (!isFinishing) insertInitialSticker()
189
+ }
190
+
191
+ private fun insertInitialSticker() {
192
+ val id = request.optString("initialStickerId").takeIf { it.isNotBlank() } ?: return
193
+ val asset = runtimeStickerAssets().firstOrNull { it.id == id } ?: return
194
+ window.setFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
195
+ Thread {
196
+ val path = try {
197
+ val localPath = if (asset.uri.startsWith("https://", ignoreCase = true)) {
198
+ val output = File.createTempFile("pve_initial_sticker_", ".img", cacheDir)
199
+ val connection = java.net.URL(asset.uri).openConnection().apply {
200
+ connectTimeout = 10_000
201
+ readTimeout = 15_000
202
+ }
203
+ connection.getInputStream().use { input -> output.outputStream().use { input.copyTo(it) } }
204
+ output.absolutePath
205
+ } else SourceResolver.resolvePath(this, asset.uri, "pve_initial_sticker")
206
+ localPath?.takeIf {
207
+ val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
208
+ BitmapFactory.decodeFile(it, options)
209
+ options.outWidth > 0 && options.outHeight > 0
210
+ }
211
+ } catch (_: Exception) { null }
212
+ runOnUiThread {
213
+ window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
214
+ if (isFinishing || isDestroyed) return@runOnUiThread
215
+ if (path == null) {
216
+ finishWithError("The initial sticker could not be loaded.", "E_SOURCE_UNREADABLE")
217
+ return@runOnUiThread
218
+ }
219
+ window.decorView.post {
220
+ if (isFinishing || isDestroyed) return@post
221
+ val layer = newSticker(stickerId = id, stickerUri = Uri.fromFile(File(path)).toString())
222
+ if (mediaType == "photo") {
223
+ photoSession?.layerStack?.commit { it + layer }
224
+ selectLayer(layer.id)
225
+ } else videoSession?.let { session ->
226
+ session.layerStack.commit { it + layer }
227
+ onVideoLayerStackChanged()
228
+ selectVideoLayer(layer.id, session)
229
+ }
230
+ }
231
+ }
232
+ }.start()
188
233
  }
189
234
 
190
235
  private fun applyStatusBarStyle() {
@@ -15,6 +15,13 @@ object SourceResolver {
15
15
  val uri = Uri.parse(sourceUri)
16
16
  return when (uri.scheme) {
17
17
  "file", null -> uri.path ?: sourceUri
18
+ "data" -> {
19
+ if (!sourceUri.startsWith("data:image/png;base64,")) return null
20
+ val bytes = try {
21
+ android.util.Base64.decode(sourceUri.substringAfter(','), android.util.Base64.DEFAULT)
22
+ } catch (_: IllegalArgumentException) { return null }
23
+ File.createTempFile(tempPrefix, ".png", context.cacheDir).apply { writeBytes(bytes) }.absolutePath
24
+ }
18
25
  "content" -> {
19
26
  val tempFile = File.createTempFile(tempPrefix, ".tmp", context.cacheDir)
20
27
  val copied = context.contentResolver.openInputStream(uri)?.use { input ->