@codebymanan/react-native-photo-editor 0.1.1 → 0.2.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.
@@ -17,7 +17,8 @@ Pod::Spec.new do |s|
17
17
  s.private_header_files = "ios/**/*.h"
18
18
  s.swift_version = "5.0"
19
19
 
20
- s.dependency "ZLImageEditor", "~> 3.0"
20
+ s.frameworks = "Photos"
21
+ s.dependency "ZLImageEditor", "~> 4.0"
21
22
 
22
23
  install_modules_dependencies(s)
23
24
  end
package/README.md CHANGED
@@ -20,15 +20,51 @@ const editedPath = await open({
20
20
  ```
21
21
 
22
22
  `open()` resolves with the file path of the edited image, or rejects with
23
- `E_CANCELLED` if the user backs out.
23
+ `E_CANCELLED` if the user backs out (see [Errors](#errors)).
24
24
 
25
25
  | Option | Type | Description |
26
26
  | --- | --- | --- |
27
- | `path` | `string` | **Required.** Image to edit. Accepts a local file path, a `file://` or `content://` URI, or a remote `http(s)` URL. |
28
- | `stickers` | `string[]` | Sticker image URLs shown in the sticker picker. |
27
+ | `path` | `string` | **Required.** Image to edit. Accepts a local file path, a `file://` URI, a remote `http(s)` URL, a `content://` URI (Android) or a `ph://` photo library URI (iOS, see below). |
28
+ | `stickers` | `string[]` | Sticker images shown in the sticker picker, as remote URLs or local paths. On iOS the sticker tool only appears when this is non-empty. |
29
29
  | `language` | `string` | BCP-47 tag (`'fr'`, `'pt-BR'`, `'zh-Hans'`) for the editor UI. Defaults to the device language. See [Localization](#localization). |
30
30
  | `translations` | `Record<string, string>` | Per-key string overrides. See [Localization](#localization). |
31
31
 
32
+ ### Stickers
33
+
34
+ Pass the images you want in the sticker picker as `stickers`. Each entry is a
35
+ remote `http(s)` URL or a path to a file on the device; transparent PNGs work
36
+ best.
37
+
38
+ ```js
39
+ const STICKERS = [
40
+ 'https://example.com/stickers/heart.png',
41
+ 'https://example.com/stickers/star.png',
42
+ `${RNFS.DocumentDirectoryPath}/stickers/smile.png`, // a file you saved earlier
43
+ ];
44
+
45
+ await open({ path, stickers: STICKERS });
46
+ ```
47
+
48
+ The user opens the sticker tool, taps a sticker to drop it onto the photo, then
49
+ drags, pinches and rotates it into place. Stickers load in the background, so
50
+ remote ones appear in the picker as they arrive.
51
+
52
+ Images bundled with `require('./sticker.png')` are not file paths in release
53
+ builds, so they cannot be passed directly. Host them remotely or copy them to
54
+ the device first.
55
+
56
+ When `stickers` is omitted or empty the platforms differ: **iOS** hides the
57
+ sticker tool, while **Android** shows a small built-in set of sample stickers.
58
+ Pass your own list if you want the same behaviour on both.
59
+
60
+ ### Photo library URIs on iOS
61
+
62
+ `ph://` URIs, as returned by pickers such as
63
+ `@react-native-camera-roll/camera-roll`, are loaded through the Photos
64
+ framework. iCloud-only photos are downloaded on demand. Your app needs an
65
+ `NSPhotoLibraryUsageDescription` entry in its `Info.plist`; if the user has
66
+ never been asked, the editor asks for access before loading the photo.
67
+
32
68
  ## Localization
33
69
 
34
70
  By default the editor follows the device language on both platforms. Pass
@@ -185,6 +221,48 @@ underlying editor never sets `semanticContentAttribute`. So the iOS editor lays
185
221
  out right-to-left when your app itself is running in an RTL language, and not
186
222
  because of this option.
187
223
 
224
+ ## Errors
225
+
226
+ `open()` rejects with one of these codes on `error.code`:
227
+
228
+ | Code | When |
229
+ | --- | --- |
230
+ | `E_CANCELLED` | The user closed the editor without saving. Usually not an error worth showing. |
231
+ | `E_IN_PROGRESS` | `open()` was called while another editor session was still open. |
232
+ | `E_INVALID_OPTIONS` | `path` was missing or empty. |
233
+ | `E_NO_ACTIVITY` | There was no screen to present the editor from, e.g. the app was in the background. |
234
+ | `E_FAILED` | The image could not be loaded or saved. `error.message` says which, including a missing photo library permission on iOS. |
235
+
236
+ ```js
237
+ try {
238
+ const edited = await open({ path });
239
+ } catch (e) {
240
+ if (e.code !== 'E_CANCELLED') {
241
+ console.warn(e.code, e.message);
242
+ }
243
+ }
244
+ ```
245
+
246
+ ## Testing with Jest
247
+
248
+ The native module is not available under Jest. Mock it with the bundled stub,
249
+ whose `open` resolves with a fake path and can be overridden per test:
250
+
251
+ ```js
252
+ // jest.setup.js
253
+ jest.mock('@codebymanan/react-native-photo-editor', () =>
254
+ require('@codebymanan/react-native-photo-editor/jest/mock')
255
+ );
256
+ ```
257
+
258
+ ```js
259
+ import { open } from '@codebymanan/react-native-photo-editor';
260
+
261
+ open.mockRejectedValueOnce(
262
+ Object.assign(new Error('cancelled'), { code: 'E_CANCELLED' })
263
+ );
264
+ ```
265
+
188
266
  ## Contributing
189
267
 
190
268
  - [Development workflow](CONTRIBUTING.md#development-workflow)
@@ -50,7 +50,7 @@ dependencies {
50
50
  implementation "com.facebook.react:react-android"
51
51
 
52
52
  // Editing engine
53
- implementation "com.burhanrashid52:photoeditor:3.0.2"
53
+ implementation "com.burhanrashid52:photoeditor:3.1.1"
54
54
 
55
55
  // Editor screen UI
56
56
  implementation "androidx.appcompat:appcompat:1.7.0"
@@ -55,7 +55,7 @@ class PhotoEditorModule(reactContext: ReactApplicationContext) :
55
55
  }
56
56
 
57
57
  override fun open(options: ReadableMap, promise: Promise) {
58
- val activity = currentActivity
58
+ val activity = reactApplicationContext.currentActivity
59
59
  if (activity == null) {
60
60
  promise.reject(E_NO_ACTIVITY, "No activity attached to React context")
61
61
  return
@@ -128,6 +128,7 @@ class EditImageActivity : BaseActivity(), OnPhotoEditorListener, View.OnClickLis
128
128
  .build()
129
129
 
130
130
  mPhotoEditor.setOnPhotoEditorListener(this)
131
+ updateUndoRedoState()
131
132
 
132
133
  loadSourceImage()
133
134
  }
@@ -195,10 +196,12 @@ class EditImageActivity : BaseActivity(), OnPhotoEditorListener, View.OnClickLis
195
196
 
196
197
  override fun onAddViewListener(viewType: ViewType, numberOfAddedViews: Int) {
197
198
  Log.d(TAG, "onAddViewListener() called with: viewType = [$viewType]")
199
+ updateUndoRedoState()
198
200
  }
199
201
 
200
202
  override fun onRemoveViewListener(viewType: ViewType, numberOfAddedViews: Int) {
201
203
  Log.d(TAG, "onRemoveViewListener() called with: viewType = [$viewType]")
204
+ updateUndoRedoState()
202
205
  }
203
206
 
204
207
  override fun onStartViewChangeListener(viewType: ViewType) {
@@ -207,6 +210,14 @@ class EditImageActivity : BaseActivity(), OnPhotoEditorListener, View.OnClickLis
207
210
 
208
211
  override fun onStopViewChangeListener(viewType: ViewType) {
209
212
  Log.d(TAG, "onStopViewChangeListener() called with: viewType = [$viewType]")
213
+ // A finished brush stroke clears the redo stack without firing add/remove
214
+ updateUndoRedoState()
215
+ }
216
+
217
+ /** Greys out undo/redo when there is nothing to step through, via the pe_undo/pe_redo selectors. */
218
+ private fun updateUndoRedoState() {
219
+ mImgUndo.isEnabled = mPhotoEditor.isUndoAvailable
220
+ mImgRedo.isEnabled = mPhotoEditor.isRedoAvailable
210
221
  }
211
222
 
212
223
  override fun onTouchSourceImage(event: MotionEvent) {
@@ -216,8 +227,14 @@ class EditImageActivity : BaseActivity(), OnPhotoEditorListener, View.OnClickLis
216
227
  @SuppressLint("NonConstantResourceId")
217
228
  override fun onClick(view: View) {
218
229
  when (view.id) {
219
- R.id.imgUndo -> mPhotoEditor.undo()
220
- R.id.imgRedo -> mPhotoEditor.redo()
230
+ R.id.imgUndo -> {
231
+ mPhotoEditor.undo()
232
+ updateUndoRedoState()
233
+ }
234
+ R.id.imgRedo -> {
235
+ mPhotoEditor.redo()
236
+ updateUndoRedoState()
237
+ }
221
238
 
222
239
  R.id.imgSave -> saveImage()
223
240
  R.id.imgClose -> onBackPressed()
@@ -1,9 +1,14 @@
1
+ import Photos
1
2
  import UIKit
2
3
  import ZLImageEditor
3
4
 
4
5
  @objc(PhotoEditorManager)
5
6
  public class PhotoEditorManager: NSObject {
6
7
 
8
+ /// True from the moment `open()` accepts a call until its promise settles,
9
+ /// which spans the image load as well as the editor itself. Main thread only.
10
+ private static var isSessionActive = false
11
+
7
12
  @objc public static func open(
8
13
  _ path: String,
9
14
  stickers: [String],
@@ -12,10 +17,46 @@ public class PhotoEditorManager: NSObject {
12
17
  resolve: @escaping (String) -> Void,
13
18
  reject: @escaping (String, String) -> Void
14
19
  ) {
15
- loadImage(path) { image in
20
+ DispatchQueue.main.async {
21
+ guard !isSessionActive else {
22
+ reject("E_IN_PROGRESS", "Another editor session is already open")
23
+ return
24
+ }
25
+ isSessionActive = true
26
+
27
+ // Every settlement below already happens on the main thread.
28
+ startSession(
29
+ path,
30
+ stickers: stickers,
31
+ language: language,
32
+ translations: translations,
33
+ resolve: { outputPath in
34
+ isSessionActive = false
35
+ resolve(outputPath)
36
+ },
37
+ reject: { code, message in
38
+ isSessionActive = false
39
+ reject(code, message)
40
+ })
41
+ }
42
+ }
43
+
44
+ private static func startSession(
45
+ _ path: String,
46
+ stickers: [String],
47
+ language: String?,
48
+ translations: [String: Any]?,
49
+ resolve: @escaping (String) -> Void,
50
+ reject: @escaping (String, String) -> Void
51
+ ) {
52
+ loadImage(path) { result in
16
53
  DispatchQueue.main.async {
17
- guard let image else {
18
- reject("E_FAILED", "Could not load image: \(path)")
54
+ let image: UIImage
55
+ switch result {
56
+ case .success(let loaded):
57
+ image = loaded
58
+ case .failure(let error):
59
+ reject("E_FAILED", error.message)
19
60
  return
20
61
  }
21
62
  guard let presenter = topViewController() else {
@@ -27,9 +68,12 @@ public class PhotoEditorManager: NSObject {
27
68
  .languageType(languageType(for: language))
28
69
  .customLanguageConfig(customLanguage(from: translations))
29
70
 
71
+ // Assigned on every call, nil included: the configuration is a shared
72
+ // singleton, and the editor hides the sticker tool when this is nil.
30
73
  ZLImageEditorConfiguration.default()
31
- .editImageTools([.draw, .clip, .textSticker, .mosaic, .filter, .adjust])
74
+ .editImageTools([.draw, .clip, .imageSticker, .textSticker, .mosaic, .filter, .adjust])
32
75
  .adjustTools([.brightness, .contrast, .saturation])
76
+ .imageStickerContainerView(stickers.isEmpty ? nil : StickerContainerView(urls: stickers))
33
77
 
34
78
  let editor = ZLEditImageViewController(image: image)
35
79
  editor.modalPresentationStyle = .fullScreen
@@ -112,18 +156,93 @@ public class PhotoEditorManager: NSObject {
112
156
  }
113
157
  }
114
158
 
115
- private static func loadImage(_ path: String, completion: @escaping (UIImage?) -> Void) {
116
- if path.hasPrefix("http://") || path.hasPrefix("https://") {
159
+ struct LoadError: Error {
160
+ let message: String
161
+ }
162
+
163
+ /// Loads the editor image or a sticker. `completion` may run on any queue.
164
+ static func loadImage(_ path: String, completion: @escaping (Result<UIImage, LoadError>) -> Void) {
165
+ let failed = LoadError(message: "Could not load image: \(path)")
166
+
167
+ if path.hasPrefix("ph://") {
168
+ loadPhotoLibraryImage(localIdentifier: String(path.dropFirst(5)), path: path, completion: completion)
169
+ } else if path.hasPrefix("http://") || path.hasPrefix("https://") {
117
170
  guard let url = URL(string: path) else {
118
- completion(nil)
171
+ completion(.failure(failed))
119
172
  return
120
173
  }
121
174
  URLSession.shared.dataTask(with: url) { data, _, _ in
122
- completion(data.flatMap(UIImage.init(data:)))
175
+ guard let image = data.flatMap(UIImage.init(data:)) else {
176
+ completion(.failure(failed))
177
+ return
178
+ }
179
+ completion(.success(image))
123
180
  }.resume()
124
181
  } else {
125
182
  let filePath = path.hasPrefix("file://") ? String(path.dropFirst(7)) : path
126
- completion(UIImage(contentsOfFile: filePath))
183
+ guard let image = UIImage(contentsOfFile: filePath) else {
184
+ completion(.failure(failed))
185
+ return
186
+ }
187
+ completion(.success(image))
188
+ }
189
+ }
190
+
191
+ /// Resolves a `ph://` URI, as returned by photo pickers such as
192
+ /// @react-native-camera-roll/camera-roll, through the Photos framework.
193
+ private static func loadPhotoLibraryImage(
194
+ localIdentifier: String,
195
+ path: String,
196
+ completion: @escaping (Result<UIImage, LoadError>) -> Void
197
+ ) {
198
+ withPhotoLibraryAccess { granted in
199
+ guard granted else {
200
+ completion(.failure(LoadError(
201
+ message: "Photo library access is needed to load \(path)")))
202
+ return
203
+ }
204
+
205
+ // Some pickers hand out only the asset UUID, without the "/L0/001"
206
+ // suffix that a full local identifier carries; try both.
207
+ var identifiers = [localIdentifier]
208
+ if !localIdentifier.contains("/") {
209
+ identifiers.append("\(localIdentifier)/L0/001")
210
+ }
211
+ guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: identifiers, options: nil).firstObject
212
+ else {
213
+ completion(.failure(LoadError(message: "No photo library asset found for \(path)")))
214
+ return
215
+ }
216
+
217
+ let options = PHImageRequestOptions()
218
+ // Lets iCloud-only photos download instead of failing.
219
+ options.isNetworkAccessAllowed = true
220
+ options.deliveryMode = .highQualityFormat
221
+ options.version = .current
222
+
223
+ PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) {
224
+ data, _, _, _ in
225
+ guard let image = data.flatMap(UIImage.init(data:)) else {
226
+ completion(.failure(LoadError(message: "Could not load image: \(path)")))
227
+ return
228
+ }
229
+ completion(.success(image))
230
+ }
231
+ }
232
+ }
233
+
234
+ /// Only prompts when the user has never been asked. Limited access counts as
235
+ /// granted: the asset is reachable if the user included it in the selection.
236
+ private static func withPhotoLibraryAccess(_ completion: @escaping (Bool) -> Void) {
237
+ switch PHPhotoLibrary.authorizationStatus(for: .readWrite) {
238
+ case .authorized, .limited:
239
+ completion(true)
240
+ case .notDetermined:
241
+ PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
242
+ completion(status == .authorized || status == .limited)
243
+ }
244
+ default:
245
+ completion(false)
127
246
  }
128
247
  }
129
248
 
@@ -0,0 +1,179 @@
1
+ import UIKit
2
+ import ZLImageEditor
3
+
4
+ /// Bottom sheet listing the `stickers` passed to `open()`.
5
+ ///
6
+ /// ZLImageEditor only offers its image sticker tool when a view conforming to
7
+ /// `ZLImageStickerContainerDelegate` is configured, and leaves showing, hiding
8
+ /// and populating it entirely to that view.
9
+ final class StickerContainerView: UIView, ZLImageStickerContainerDelegate {
10
+
11
+ var selectImageBlock: ((UIImage) -> Void)?
12
+ var hideBlock: (() -> Void)?
13
+
14
+ private static let sheetHeightRatio: CGFloat = 0.45
15
+ private static let cellSpacing: CGFloat = 12
16
+ private static let columns: CGFloat = 4
17
+
18
+ private let urls: [String]
19
+ /// Indexed like `urls`; nil until that sticker has loaded, and stays nil if it fails.
20
+ private var images: [UIImage?]
21
+
22
+ private let sheet = UIView()
23
+ private let collectionView: UICollectionView
24
+
25
+ init(urls: [String]) {
26
+ self.urls = urls
27
+ self.images = Array(repeating: nil, count: urls.count)
28
+
29
+ let layout = UICollectionViewFlowLayout()
30
+ layout.minimumInteritemSpacing = Self.cellSpacing
31
+ layout.minimumLineSpacing = Self.cellSpacing
32
+ layout.sectionInset = UIEdgeInsets(
33
+ top: Self.cellSpacing, left: Self.cellSpacing, bottom: Self.cellSpacing, right: Self.cellSpacing)
34
+ collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
35
+
36
+ super.init(frame: .zero)
37
+
38
+ // Tapping the dimmed area above the sheet dismisses it; the editor does not.
39
+ let dismissTap = UITapGestureRecognizer(target: self, action: #selector(hide))
40
+ dismissTap.delegate = self
41
+ addGestureRecognizer(dismissTap)
42
+
43
+ sheet.backgroundColor = UIColor(white: 0.1, alpha: 0.95)
44
+ sheet.layer.cornerRadius = 12
45
+ sheet.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
46
+ addSubview(sheet)
47
+
48
+ collectionView.backgroundColor = .clear
49
+ collectionView.dataSource = self
50
+ collectionView.delegate = self
51
+ collectionView.register(StickerCell.self, forCellWithReuseIdentifier: StickerCell.reuseIdentifier)
52
+ sheet.addSubview(collectionView)
53
+
54
+ loadStickers()
55
+ }
56
+
57
+ @available(*, unavailable)
58
+ required init?(coder: NSCoder) {
59
+ fatalError("init(coder:) has not been implemented")
60
+ }
61
+
62
+ // MARK: - ZLImageStickerContainerDelegate
63
+
64
+ func show(in view: UIView) {
65
+ if superview !== view {
66
+ removeFromSuperview()
67
+ view.addSubview(self)
68
+ }
69
+ frame = view.bounds
70
+ autoresizingMask = [.flexibleWidth, .flexibleHeight]
71
+ isHidden = false
72
+ layoutIfNeeded()
73
+
74
+ sheet.transform = CGAffineTransform(translationX: 0, y: sheet.bounds.height)
75
+ backgroundColor = .clear
76
+ UIView.animate(withDuration: 0.25) {
77
+ self.sheet.transform = .identity
78
+ self.backgroundColor = UIColor(white: 0, alpha: 0.3)
79
+ }
80
+ }
81
+
82
+ @objc private func hide() {
83
+ UIView.animate(withDuration: 0.25, animations: {
84
+ self.sheet.transform = CGAffineTransform(translationX: 0, y: self.sheet.bounds.height)
85
+ self.backgroundColor = .clear
86
+ }, completion: { _ in
87
+ self.isHidden = true
88
+ self.sheet.transform = .identity
89
+ })
90
+ hideBlock?()
91
+ }
92
+
93
+ // MARK: - Layout
94
+
95
+ override func layoutSubviews() {
96
+ super.layoutSubviews()
97
+ let height = bounds.height * Self.sheetHeightRatio
98
+ sheet.frame = CGRect(x: 0, y: bounds.height - height, width: bounds.width, height: height)
99
+ collectionView.frame = sheet.bounds
100
+ collectionView.contentInset.bottom = safeAreaInsets.bottom
101
+ }
102
+
103
+ // MARK: - Loading
104
+
105
+ private func loadStickers() {
106
+ for (index, url) in urls.enumerated() {
107
+ PhotoEditorManager.loadImage(url) { [weak self] result in
108
+ guard case .success(let image) = result else { return }
109
+ DispatchQueue.main.async {
110
+ guard let self else { return }
111
+ self.images[index] = image
112
+ self.collectionView.reloadItems(at: [IndexPath(item: index, section: 0)])
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ extension StickerContainerView: UIGestureRecognizerDelegate {
120
+ func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
121
+ !sheet.frame.contains(touch.location(in: self))
122
+ }
123
+ }
124
+
125
+ extension StickerContainerView: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
126
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
127
+ urls.count
128
+ }
129
+
130
+ func collectionView(
131
+ _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath
132
+ ) -> UICollectionViewCell {
133
+ let cell = collectionView.dequeueReusableCell(
134
+ withReuseIdentifier: StickerCell.reuseIdentifier, for: indexPath) as! StickerCell
135
+ cell.imageView.image = images[indexPath.item]
136
+ return cell
137
+ }
138
+
139
+ func collectionView(
140
+ _ collectionView: UICollectionView,
141
+ layout collectionViewLayout: UICollectionViewLayout,
142
+ sizeForItemAt indexPath: IndexPath
143
+ ) -> CGSize {
144
+ let gaps = Self.cellSpacing * (Self.columns + 1)
145
+ let side = floor((collectionView.bounds.width - gaps) / Self.columns)
146
+ return CGSize(width: side, height: side)
147
+ }
148
+
149
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
150
+ // A sticker that has not loaded (or failed to) has nothing to add.
151
+ guard let image = images[indexPath.item] else { return }
152
+ selectImageBlock?(image)
153
+ hide()
154
+ }
155
+ }
156
+
157
+ private final class StickerCell: UICollectionViewCell {
158
+ static let reuseIdentifier = "StickerCell"
159
+
160
+ let imageView = UIImageView()
161
+
162
+ override init(frame: CGRect) {
163
+ super.init(frame: frame)
164
+ imageView.contentMode = .scaleAspectFit
165
+ imageView.frame = contentView.bounds
166
+ imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
167
+ contentView.addSubview(imageView)
168
+ }
169
+
170
+ @available(*, unavailable)
171
+ required init?(coder: NSCoder) {
172
+ fatalError("init(coder:) has not been implemented")
173
+ }
174
+
175
+ override func prepareForReuse() {
176
+ super.prepareForReuse()
177
+ imageView.image = nil
178
+ }
179
+ }
package/jest/mock.js ADDED
@@ -0,0 +1,7 @@
1
+ /* global jest */
2
+ // Stands in for the native module in Jest, where TurboModules are unavailable.
3
+ // Usage: jest.mock('@codebymanan/react-native-photo-editor', () =>
4
+ // require('@codebymanan/react-native-photo-editor/jest/mock'));
5
+ module.exports = {
6
+ open: jest.fn(() => Promise.resolve('/tmp/photo_editor/edited.png')),
7
+ };
@@ -1,11 +1,13 @@
1
1
  export interface PhotoEditorOptions {
2
2
  /**
3
- * The image to edit. Accepts a local file path, `file://` or `content://`
4
- * URI, or a remote `http(s)` URL.
3
+ * The image to edit. Accepts a local file path, `file://` URI, or a remote
4
+ * `http(s)` URL, plus `content://` URIs on Android and `ph://` photo library
5
+ * URIs on iOS.
5
6
  */
6
7
  path: string;
7
8
  /**
8
- * Optional list of sticker image URLs shown in the sticker picker.
9
+ * Optional list of sticker images (remote URLs or local paths) shown in the
10
+ * sticker picker. On iOS the sticker tool only appears when this is set.
9
11
  */
10
12
  stickers?: string[];
11
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CAC1C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CAC1C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebymanan/react-native-photo-editor",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "A React Native photo editor plugin wrapping native editors for Android and iOS",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -10,6 +10,7 @@
10
10
  "types": "./lib/typescript/src/index.d.ts",
11
11
  "default": "./lib/module/index.js"
12
12
  },
13
+ "./jest/mock": "./jest/mock.js",
13
14
  "./package.json": "./package.json"
14
15
  },
15
16
  "files": [
@@ -20,6 +21,7 @@
20
21
  "cpp",
21
22
  "*.podspec",
22
23
  "react-native.config.js",
24
+ "jest",
23
25
  "!ios/build",
24
26
  "!android/build",
25
27
  "!android/gradle",
package/src/types.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  export interface PhotoEditorOptions {
2
2
  /**
3
- * The image to edit. Accepts a local file path, `file://` or `content://`
4
- * URI, or a remote `http(s)` URL.
3
+ * The image to edit. Accepts a local file path, `file://` URI, or a remote
4
+ * `http(s)` URL, plus `content://` URIs on Android and `ph://` photo library
5
+ * URIs on iOS.
5
6
  */
6
7
  path: string;
7
8
  /**
8
- * Optional list of sticker image URLs shown in the sticker picker.
9
+ * Optional list of sticker images (remote URLs or local paths) shown in the
10
+ * sticker picker. On iOS the sticker tool only appears when this is set.
9
11
  */
10
12
  stickers?: string[];
11
13
  /**