@phucprime/react-native-image-editor 1.0.3 → 1.0.4
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/README.md +51 -31
- package/android/build.gradle +5 -1
- package/android/src/main/java/ui/photoeditor/RNPhotoEditorModule.java +155 -60
- package/android/src/main/java/ui/photoeditor/RNPhotoEditorPackage.java +50 -12
- package/android/src/newarch/java/ui/photoeditor/RNPhotoEditorSpec.java +10 -2
- package/android/src/oldarch/java/ui/photoeditor/RNPhotoEditorSpec.java +22 -4
- package/ios/RNImageEditor.podspec +39 -8
- package/ios/RNPhotoEditor.mm +32 -6
- package/ios/RNPhotoEditor.swift +178 -95
- package/lib/commonjs/NativeRNPhotoEditor.js +41 -0
- package/lib/commonjs/NativeRNPhotoEditor.js.map +1 -0
- package/lib/commonjs/index.js +142 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/module/NativeRNPhotoEditor.js +40 -0
- package/lib/module/NativeRNPhotoEditor.js.map +1 -0
- package/lib/module/index.js +140 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/NativeRNPhotoEditor.d.ts +46 -0
- package/lib/typescript/NativeRNPhotoEditor.d.ts.map +1 -0
- package/lib/{index.d.ts → typescript/index.d.ts} +44 -34
- package/lib/typescript/index.d.ts.map +1 -0
- package/package.json +49 -18
- package/src/NativeRNPhotoEditor.ts +38 -8
- package/src/index.ts +85 -80
- package/android/libs/photo-editor-android-original.jar +0 -0
- package/lib/NativeRNPhotoEditor.d.ts +0 -14
- package/lib/NativeRNPhotoEditor.d.ts.map +0 -1
- package/lib/NativeRNPhotoEditor.js +0 -5
- package/lib/NativeRNPhotoEditor.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -120
- package/lib/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,34 +1,50 @@
|
|
|
1
1
|
<h1 align="center">@phucprime/react-native-image-editor</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
A high-performance, native image editing library for React Native — crop, draw, text, stickers, and more.<br/>
|
|
5
|
+
Fully supports the <strong>New Architecture</strong> (Fabric + TurboModules) and legacy bridge mode.
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
9
|
<a href="https://www.npmjs.com/package/@phucprime/react-native-image-editor"><img src="https://img.shields.io/npm/v/@phucprime/react-native-image-editor.svg?style=flat-square" alt="npm version" /></a>
|
|
10
10
|
<a href="https://www.npmjs.com/package/@phucprime/react-native-image-editor"><img src="https://img.shields.io/npm/dm/@phucprime/react-native-image-editor.svg?style=flat-square" alt="npm downloads" /></a>
|
|
11
11
|
<a href="https://github.com/phucprime/react-native-image-editor/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@phucprime/react-native-image-editor.svg?style=flat-square" alt="license" /></a>
|
|
12
|
+
<a href="https://reactnative.dev/docs/the-new-architecture/landing-page"><img src="https://img.shields.io/badge/React%20Native-New%20Architecture-blue?style=flat-square" alt="New Architecture Ready" /></a>
|
|
12
13
|
</p>
|
|
13
14
|
|
|
14
15
|
---
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
## ⚡ Features
|
|
18
|
+
|
|
19
|
+
- ✂️ **Image Cropping**: Precision native cropping UI.
|
|
20
|
+
- 🎨 **Freehand Drawing**: Brush tool with custom color choices.
|
|
21
|
+
- ✍️ **Text Overlays**: Add, position, scale, and rotate text with custom colors.
|
|
22
|
+
- 🎭 **Stickers**: Add customizable sticker overlays.
|
|
23
|
+
- 🚀 **TurboModules Support**: Native performance on React Native New Architecture.
|
|
24
|
+
|
|
25
|
+
## 📱 Screenshots
|
|
26
|
+
|
|
27
|
+
| iOS | Android |
|
|
28
|
+
| :--------------------------------------: | :------------------------------------------: |
|
|
18
29
|
| <img src="assets/ios.gif" width="280" /> | <img src="assets/android.gif" width="280" /> |
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 📋 Compatibility
|
|
21
34
|
|
|
22
|
-
| | Minimum
|
|
23
|
-
|
|
|
24
|
-
| React Native | 0.73
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
35
|
+
| Platform / Tool | Minimum | Tested |
|
|
36
|
+
| ---------------- | ----------- | --------------- |
|
|
37
|
+
| **React Native** | `>= 0.73` | **`0.78.2`** |
|
|
38
|
+
| **React** | `>= 18.2.0` | **`19.0.0`** |
|
|
39
|
+
| **iOS** | `13.0` | `18.0` |
|
|
40
|
+
| **Android** | API 24 | API 35 |
|
|
41
|
+
| **Architecture** | Old Arch ✅ | **New Arch ✅** |
|
|
28
42
|
|
|
29
|
-
> Requires **JDK 17** and **Gradle 8.x** for Android builds.
|
|
43
|
+
> **Note:** Requires **JDK 17** and **Gradle 8.x** for Android builds.
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 📦 Installation
|
|
32
48
|
|
|
33
49
|
```bash
|
|
34
50
|
npm install @phucprime/react-native-image-editor
|
|
@@ -75,7 +91,7 @@ Add activities to `AndroidManifest.xml`:
|
|
|
75
91
|
<activity android:name="com.yalantis.ucrop.UCropActivity" />
|
|
76
92
|
```
|
|
77
93
|
|
|
78
|
-
## Usage
|
|
94
|
+
## 🛠️ Usage
|
|
79
95
|
|
|
80
96
|
### Promise API (recommended)
|
|
81
97
|
|
|
@@ -98,29 +114,33 @@ ImageEditor.open({
|
|
|
98
114
|
});
|
|
99
115
|
```
|
|
100
116
|
|
|
101
|
-
## Options
|
|
117
|
+
## ⚙️ Configuration Options
|
|
118
|
+
|
|
119
|
+
| Property | Type | Default | Description |
|
|
120
|
+
| ---------------- | ---------- | --------------- | ------------------------------------------------------------------------------------------------------ |
|
|
121
|
+
| `path` | `string` | **Required** | Local file URI/path of the image to edit. |
|
|
122
|
+
| `colors` | `string[]` | Default palette | Array of hex color strings for drawing and text. |
|
|
123
|
+
| `stickers` | `string[]` | `[]` | List of sticker image names located in native resource folders. |
|
|
124
|
+
| `hiddenControls` | `string[]` | `[]` | Array of controls to hide (`'text'`, `'clear'`, `'draw'`, `'save'`, `'share'`, `'sticker'`, `'crop'`). |
|
|
125
|
+
| `languages` | `object` | English | Object map for localized UI text strings. |
|
|
102
126
|
|
|
103
|
-
|
|
104
|
-
| -------- | ---- | ----------- |
|
|
105
|
-
| `path` | `string` | Path to the image file |
|
|
106
|
-
| `colors` | `string[]` | Hex colors for draw/text palette |
|
|
107
|
-
| `stickers` | `string[]` | Sticker image names from native resources |
|
|
108
|
-
| `hiddenControls` | `string[]` | Hide: `'text'` `'clear'` `'draw'` `'save'` `'share'` `'sticker'` `'crop'` |
|
|
109
|
-
| `languages` | `object` | UI localization strings |
|
|
127
|
+
> **Tip:** Use [react-native-fs](https://github.com/itinance/react-native-fs) or `@react-native-camera-roll/camera-roll` to handle native file paths in sandbox directories before passing them to the editor.
|
|
110
128
|
|
|
111
|
-
|
|
129
|
+
---
|
|
112
130
|
|
|
113
|
-
## Stickers
|
|
131
|
+
## 🎨 Adding Custom Stickers
|
|
114
132
|
|
|
115
|
-
- **iOS
|
|
116
|
-
- **Android
|
|
133
|
+
- **iOS**: Add image assets (`.png`) to your main Xcode Project's **Resources** bundle.
|
|
134
|
+
- **Android**: Add image assets (`.png`) to `android/app/src/main/res/drawable/`.
|
|
117
135
|
|
|
118
|
-
|
|
136
|
+
##### Refer to the [Example](Example/) project for a ready-to-run demonstration setup.
|
|
119
137
|
|
|
120
|
-
##
|
|
138
|
+
## 📄 License
|
|
121
139
|
|
|
122
|
-
|
|
140
|
+
Distributed under the [Apache 2.0 License](LICENSE).
|
|
141
|
+
|
|
142
|
+
---
|
|
123
143
|
|
|
124
|
-
##
|
|
144
|
+
## 🙏 Credits
|
|
125
145
|
|
|
126
|
-
[
|
|
146
|
+
Maintained by **[nguyenhoangphucvnm](https://github.com/nguyenhoangphucvnm)**. Based on [prscX/react-native-photo-editor](https://github.com/prscX/react-native-photo-editor), built upon native photo editor SDKs.
|
package/android/build.gradle
CHANGED
|
@@ -68,7 +68,11 @@ repositories {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
dependencies {
|
|
71
|
-
|
|
71
|
+
// Use compileOnly with the RN 0.71+ artifact name.
|
|
72
|
+
// 'react-native:+' open range risks version skew between codegen output
|
|
73
|
+
// and runtime; compileOnly ensures the host app's own react-android
|
|
74
|
+
// version is used at runtime without pulling in a second copy.
|
|
75
|
+
compileOnly 'com.facebook.react:react-android'
|
|
72
76
|
implementation files('libs/photo-editor-android.jar')
|
|
73
77
|
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1@aar'
|
|
74
78
|
implementation 'com.google.android.material:material:1.9.0'
|
|
@@ -3,105 +3,200 @@ package ui.photoeditor;
|
|
|
3
3
|
import android.app.Activity;
|
|
4
4
|
import android.content.Intent;
|
|
5
5
|
import android.graphics.Color;
|
|
6
|
+
|
|
7
|
+
import androidx.annotation.NonNull;
|
|
8
|
+
import androidx.annotation.Nullable;
|
|
9
|
+
|
|
6
10
|
import com.ahmedadeltito.photoeditor.PhotoEditorActivity;
|
|
7
11
|
import com.ahmedadeltito.photoeditor.TranslationService;
|
|
12
|
+
|
|
8
13
|
import com.facebook.react.bridge.ActivityEventListener;
|
|
9
14
|
import com.facebook.react.bridge.BaseActivityEventListener;
|
|
10
|
-
import com.facebook.react.bridge.
|
|
15
|
+
import com.facebook.react.bridge.Promise;
|
|
11
16
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
12
17
|
import com.facebook.react.bridge.ReactMethod;
|
|
13
18
|
import com.facebook.react.bridge.ReadableArray;
|
|
14
19
|
import com.facebook.react.bridge.ReadableMap;
|
|
20
|
+
|
|
15
21
|
import java.util.ArrayList;
|
|
16
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Native module that opens a full-screen photo editor Activity and returns the
|
|
25
|
+
* saved image path (or a cancellation rejection) via a {@link Promise}.
|
|
26
|
+
*
|
|
27
|
+
* Promise vs Callback
|
|
28
|
+
* ───────────────────
|
|
29
|
+
* The previous implementation stored two {@code Callback} instances as instance
|
|
30
|
+
* fields and invoked them from {@code onActivityResult}. On New Architecture,
|
|
31
|
+
* {@code Callback} objects are backed by {@code CallbackHolder} references tied
|
|
32
|
+
* to the JS context. If the JS context reloads (Fast Refresh, error recovery)
|
|
33
|
+
* while the editor Activity is open, those stored callbacks become dangling
|
|
34
|
+
* references — invoking them is either a no-op or a crash.
|
|
35
|
+
*
|
|
36
|
+
* {@link Promise} has the same lifetime guarantee but is explicitly designed for
|
|
37
|
+
* single-shot async operations that span multiple native frames/events. It is
|
|
38
|
+
* also the only pattern fully validated by the New Architecture codegen.
|
|
39
|
+
*
|
|
40
|
+
* Thread safety
|
|
41
|
+
* ─────────────
|
|
42
|
+
* {@code mPendingPromise} is written on the JS thread (inside {@code edit()})
|
|
43
|
+
* and read/nulled on the UI thread (inside {@code onActivityResult}). Both
|
|
44
|
+
* accesses are guarded by synchronisation on {@code this}, and the field is
|
|
45
|
+
* marked {@code volatile} so the write is immediately visible across threads.
|
|
46
|
+
*/
|
|
17
47
|
public class RNPhotoEditorModule extends RNPhotoEditorSpec {
|
|
18
48
|
|
|
49
|
+
/** Module name as registered in JS — must match TurboModuleRegistry.getEnforcing('RNPhotoEditor'). */
|
|
50
|
+
public static final String NAME = "RNPhotoEditor";
|
|
51
|
+
|
|
52
|
+
/** startActivityForResult request code — arbitrary, just needs to be unique per Activity. */
|
|
19
53
|
private static final int PHOTO_EDITOR_REQUEST = 1;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
54
|
+
|
|
55
|
+
/** Error code returned when the user cancels the editor. */
|
|
56
|
+
private static final String E_CANCELLED = "CANCELLED";
|
|
57
|
+
|
|
58
|
+
/** The in-flight Promise. Volatile for cross-thread visibility; guarded by synchronized(this). */
|
|
59
|
+
private volatile @Nullable Promise mPendingPromise;
|
|
60
|
+
|
|
61
|
+
// ─── Activity result listener ─────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
private final ActivityEventListener mActivityEventListener =
|
|
64
|
+
new BaseActivityEventListener() {
|
|
65
|
+
@Override
|
|
66
|
+
public void onActivityResult(
|
|
67
|
+
Activity activity,
|
|
68
|
+
int requestCode,
|
|
69
|
+
int resultCode,
|
|
70
|
+
@Nullable Intent intent) {
|
|
71
|
+
|
|
72
|
+
if (requestCode != PHOTO_EDITOR_REQUEST) return;
|
|
73
|
+
|
|
74
|
+
Promise promise;
|
|
75
|
+
synchronized (RNPhotoEditorModule.this) {
|
|
76
|
+
promise = mPendingPromise;
|
|
77
|
+
mPendingPromise = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (promise == null) return; // JS context already gone — nothing to do
|
|
81
|
+
|
|
35
82
|
if (resultCode == Activity.RESULT_CANCELED) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
83
|
+
promise.reject(E_CANCELLED, "User cancelled the editor");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// RESULT_OK — extract the saved path from the result Intent
|
|
88
|
+
if (intent == null || intent.getExtras() == null) {
|
|
89
|
+
promise.reject("NO_RESULT", "Editor returned no data");
|
|
90
|
+
return;
|
|
39
91
|
}
|
|
92
|
+
|
|
93
|
+
String imagePath = intent.getExtras().getString("imagePath");
|
|
94
|
+
if (imagePath == null) {
|
|
95
|
+
promise.reject("NO_PATH", "Editor returned null image path");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
promise.resolve(imagePath);
|
|
40
100
|
}
|
|
101
|
+
};
|
|
41
102
|
|
|
42
|
-
|
|
43
|
-
mDoneCallback = null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
};
|
|
103
|
+
// ─── Constructor ──────────────────────────────────────────────────────────
|
|
47
104
|
|
|
48
105
|
public RNPhotoEditorModule(ReactApplicationContext reactContext) {
|
|
49
106
|
super(reactContext);
|
|
50
107
|
reactContext.addActivityEventListener(mActivityEventListener);
|
|
51
108
|
}
|
|
52
109
|
|
|
110
|
+
// ─── TurboModule contract ─────────────────────────────────────────────────
|
|
111
|
+
|
|
53
112
|
@Override
|
|
113
|
+
@NonNull
|
|
54
114
|
public String getName() {
|
|
55
|
-
return
|
|
115
|
+
return NAME;
|
|
56
116
|
}
|
|
57
117
|
|
|
118
|
+
// ─── @ReactMethod ─────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Open the photo editor.
|
|
122
|
+
*
|
|
123
|
+
* Codegen maps this to the {@code edit(props: UnsafeObject): Promise<string>}
|
|
124
|
+
* spec defined in {@code NativeRNPhotoEditor.ts}.
|
|
125
|
+
*
|
|
126
|
+
* @param props Editor configuration (path, colors, stickers, hiddenControls, languages).
|
|
127
|
+
* @param promise Resolved with the saved image path; rejected with code
|
|
128
|
+
* {@value E_CANCELLED} when the user dismisses.
|
|
129
|
+
*/
|
|
58
130
|
@ReactMethod
|
|
59
|
-
public void
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
stickersIntent.add(drawableId);
|
|
131
|
+
public void edit(@NonNull ReadableMap props, @NonNull Promise promise) {
|
|
132
|
+
|
|
133
|
+
// ── Guard: reject immediately if there is already an editor open ──────
|
|
134
|
+
synchronized (this) {
|
|
135
|
+
if (mPendingPromise != null) {
|
|
136
|
+
promise.reject("ALREADY_OPEN", "An editor session is already in progress");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
mPendingPromise = promise;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Guard: need a live Activity to start the editor ───────────────────
|
|
143
|
+
Activity activity = getCurrentActivity();
|
|
144
|
+
if (activity == null) {
|
|
145
|
+
synchronized (this) { mPendingPromise = null; }
|
|
146
|
+
promise.reject("NO_ACTIVITY", "No active Android Activity found");
|
|
147
|
+
return;
|
|
78
148
|
}
|
|
79
149
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
150
|
+
// ── Translations ──────────────────────────────────────────────────────
|
|
151
|
+
ReadableMap languages = props.getMap("languages");
|
|
152
|
+
if (languages != null) {
|
|
153
|
+
TranslationService.getInstance().init(languages.toHashMap());
|
|
154
|
+
}
|
|
83
155
|
|
|
84
|
-
|
|
85
|
-
|
|
156
|
+
// ── Stickers: string names → drawable resource IDs ────────────────────
|
|
157
|
+
ReadableArray stickersArr = props.getArray("stickers");
|
|
158
|
+
ArrayList<Integer> stickersIntent = new ArrayList<>();
|
|
159
|
+
if (stickersArr != null) {
|
|
160
|
+
for (int i = 0; i < stickersArr.size(); i++) {
|
|
161
|
+
int id = getReactApplicationContext()
|
|
162
|
+
.getResources()
|
|
163
|
+
.getIdentifier(
|
|
164
|
+
stickersArr.getString(i),
|
|
165
|
+
"drawable",
|
|
166
|
+
getReactApplicationContext().getPackageName());
|
|
167
|
+
stickersIntent.add(id);
|
|
168
|
+
}
|
|
86
169
|
}
|
|
87
170
|
|
|
88
|
-
//
|
|
89
|
-
ReadableArray
|
|
90
|
-
ArrayList
|
|
171
|
+
// ── Hidden controls ───────────────────────────────────────────────────
|
|
172
|
+
ReadableArray hiddenArr = props.getArray("hiddenControls");
|
|
173
|
+
ArrayList<String> hiddenControlsIntent = new ArrayList<>();
|
|
174
|
+
if (hiddenArr != null) {
|
|
175
|
+
for (int i = 0; i < hiddenArr.size(); i++) {
|
|
176
|
+
hiddenControlsIntent.add(hiddenArr.getString(i));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
91
179
|
|
|
92
|
-
|
|
93
|
-
|
|
180
|
+
// ── Colors: hex strings → android.graphics.Color ints ────────────────
|
|
181
|
+
ReadableArray colorsArr = props.getArray("colors");
|
|
182
|
+
ArrayList<Integer> colorPickerColors = new ArrayList<>();
|
|
183
|
+
if (colorsArr != null) {
|
|
184
|
+
for (int i = 0; i < colorsArr.size(); i++) {
|
|
185
|
+
try {
|
|
186
|
+
colorPickerColors.add(Color.parseColor(colorsArr.getString(i)));
|
|
187
|
+
} catch (IllegalArgumentException ignored) {
|
|
188
|
+
// Skip invalid color strings rather than crashing
|
|
189
|
+
}
|
|
190
|
+
}
|
|
94
191
|
}
|
|
95
192
|
|
|
96
|
-
|
|
97
|
-
intent
|
|
193
|
+
// ── Launch editor Activity ─────────────────────────────────────────────
|
|
194
|
+
Intent intent = new Intent(activity, PhotoEditorActivity.class);
|
|
195
|
+
intent.putExtra("selectedImagePath", props.getString("path"));
|
|
98
196
|
intent.putExtra("colorPickerColors", colorPickerColors);
|
|
99
197
|
intent.putExtra("hiddenControls", hiddenControlsIntent);
|
|
100
198
|
intent.putExtra("stickers", stickersIntent);
|
|
101
199
|
|
|
102
|
-
|
|
103
|
-
mDoneCallback = onDone;
|
|
104
|
-
|
|
105
|
-
getCurrentActivity().startActivityForResult(intent, PHOTO_EDITOR_REQUEST);
|
|
200
|
+
activity.startActivityForResult(intent, PHOTO_EDITOR_REQUEST);
|
|
106
201
|
}
|
|
107
202
|
}
|
|
@@ -1,23 +1,61 @@
|
|
|
1
|
-
|
|
2
1
|
package ui.photoeditor;
|
|
3
2
|
|
|
4
|
-
import
|
|
5
|
-
import java.util.Collections;
|
|
6
|
-
import java.util.List;
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
7
4
|
|
|
8
|
-
import com.facebook.react.
|
|
5
|
+
import com.facebook.react.TurboReactPackage;
|
|
9
6
|
import com.facebook.react.bridge.NativeModule;
|
|
10
7
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
11
|
-
import com.facebook.react.
|
|
8
|
+
import com.facebook.react.module.model.ReactModuleInfo;
|
|
9
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
10
|
+
|
|
11
|
+
import java.util.HashMap;
|
|
12
|
+
import java.util.Map;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ReactPackage registration for RNPhotoEditor.
|
|
16
|
+
*
|
|
17
|
+
* Extends {@link TurboReactPackage} instead of the old {@link com.facebook.react.ReactPackage}.
|
|
18
|
+
*
|
|
19
|
+
* Why TurboReactPackage?
|
|
20
|
+
* ──────────────────────
|
|
21
|
+
* On New Architecture, React Native discovers TurboModules through the
|
|
22
|
+
* {@link TurboReactPackage#getModule} / {@link TurboReactPackage#getReactModuleInfoProvider}
|
|
23
|
+
* contract. A plain {@code ReactPackage} that only overrides
|
|
24
|
+
* {@code createNativeModules()} is never consulted by the TurboModule registry,
|
|
25
|
+
* so {@code TurboModuleRegistry.getEnforcing("RNPhotoEditor")} would throw
|
|
26
|
+
* "No TurboModule found for RNPhotoEditor" at runtime on New Arch.
|
|
27
|
+
*
|
|
28
|
+
* On Old Architecture, {@link TurboReactPackage} delegates back to
|
|
29
|
+
* {@code getModule()} so no separate code path is needed.
|
|
30
|
+
*/
|
|
31
|
+
public class RNPhotoEditorPackage extends TurboReactPackage {
|
|
12
32
|
|
|
13
|
-
public class RNPhotoEditorPackage implements ReactPackage {
|
|
14
33
|
@Override
|
|
15
|
-
public
|
|
16
|
-
|
|
34
|
+
public NativeModule getModule(
|
|
35
|
+
@NonNull String name,
|
|
36
|
+
@NonNull ReactApplicationContext reactContext) {
|
|
37
|
+
if (RNPhotoEditorModule.NAME.equals(name)) {
|
|
38
|
+
return new RNPhotoEditorModule(reactContext);
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
17
41
|
}
|
|
18
42
|
|
|
19
43
|
@Override
|
|
20
|
-
public
|
|
21
|
-
|
|
44
|
+
public ReactModuleInfoProvider getReactModuleInfoProvider() {
|
|
45
|
+
return () -> {
|
|
46
|
+
Map<String, ReactModuleInfo> map = new HashMap<>();
|
|
47
|
+
map.put(
|
|
48
|
+
RNPhotoEditorModule.NAME,
|
|
49
|
+
new ReactModuleInfo(
|
|
50
|
+
RNPhotoEditorModule.NAME, // name
|
|
51
|
+
RNPhotoEditorModule.NAME, // className (used for logging)
|
|
52
|
+
false, // canOverrideExistingModule
|
|
53
|
+
false, // needsEagerInit
|
|
54
|
+
false, // isCxxModule
|
|
55
|
+
true // isTurboModule — registers with the JSI TurboModule registry
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
return map;
|
|
59
|
+
};
|
|
22
60
|
}
|
|
23
|
-
}
|
|
61
|
+
}
|
|
@@ -4,8 +4,16 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* New Architecture spec wrapper.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* On New Architecture, the React Native codegen generates
|
|
9
|
+
* {@code NativeRNPhotoEditorSpec} from {@code src/NativeRNPhotoEditor.ts}.
|
|
10
|
+
* That generated class already extends {@code ReactContextBaseJavaModule} and
|
|
11
|
+
* declares the abstract {@code edit(ReadableMap props, Promise promise)} method.
|
|
12
|
+
*
|
|
13
|
+
* This thin wrapper simply inherits from the generated spec so that
|
|
14
|
+
* {@link ui.photoeditor.RNPhotoEditorModule} can extend a single
|
|
15
|
+
* {@code RNPhotoEditorSpec} type regardless of architecture, keeping
|
|
16
|
+
* {@code RNPhotoEditorModule.java} in the shared {@code main} source set.
|
|
9
17
|
*/
|
|
10
18
|
abstract class RNPhotoEditorSpec extends NativeRNPhotoEditorSpec {
|
|
11
19
|
RNPhotoEditorSpec(ReactApplicationContext context) {
|
|
@@ -1,19 +1,37 @@
|
|
|
1
1
|
package ui.photoeditor;
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
|
|
5
|
+
import com.facebook.react.bridge.Promise;
|
|
4
6
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
5
7
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
6
8
|
import com.facebook.react.bridge.ReadableMap;
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Old Architecture compatibility spec.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
*
|
|
13
|
+
* On Old Architecture (classic bridge), there is no codegen-generated base
|
|
14
|
+
* class, so we hand-write the abstract contract here.
|
|
15
|
+
*
|
|
16
|
+
* The method signature mirrors the New Architecture codegen output exactly:
|
|
17
|
+
* {@code edit(ReadableMap props, Promise promise)}
|
|
18
|
+
*
|
|
19
|
+
* This means {@link ui.photoeditor.RNPhotoEditorModule} compiles and behaves
|
|
20
|
+
* identically regardless of which source set ({@code newarch} / {@code oldarch})
|
|
21
|
+
* is active.
|
|
12
22
|
*/
|
|
13
23
|
abstract class RNPhotoEditorSpec extends ReactContextBaseJavaModule {
|
|
24
|
+
|
|
14
25
|
RNPhotoEditorSpec(ReactApplicationContext context) {
|
|
15
26
|
super(context);
|
|
16
27
|
}
|
|
17
28
|
|
|
18
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Open the photo editor.
|
|
31
|
+
*
|
|
32
|
+
* @param props Editor configuration map (path, colors, stickers, …).
|
|
33
|
+
* @param promise Resolved with the saved image path on success; rejected
|
|
34
|
+
* with code {@code "CANCELLED"} when the user dismisses.
|
|
35
|
+
*/
|
|
36
|
+
public abstract void edit(@NonNull ReadableMap props, @NonNull Promise promise);
|
|
19
37
|
}
|
|
@@ -3,24 +3,55 @@ require "json"
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, "../package.json")))
|
|
4
4
|
|
|
5
5
|
Pod::Spec.new do |s|
|
|
6
|
-
s.name
|
|
7
|
-
s.version
|
|
8
|
-
s.summary
|
|
6
|
+
s.name = "RNImageEditor"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
9
|
s.description = package["description"]
|
|
10
|
-
s.homepage
|
|
11
|
-
s.license
|
|
12
|
-
s.author
|
|
13
|
-
s.source
|
|
10
|
+
s.homepage = package["homepage"]
|
|
11
|
+
s.license = package["license"]
|
|
12
|
+
s.author = package["author"]
|
|
13
|
+
s.source = {
|
|
14
|
+
:git => "https://github.com/nguyenhoangphucvnm/react-native-image-editor.git",
|
|
15
|
+
:tag => s.version
|
|
16
|
+
}
|
|
14
17
|
|
|
15
18
|
s.platforms = { :ios => "13.0" }
|
|
16
19
|
|
|
17
20
|
s.preserve_paths = "LICENSE", "package.json"
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
# Include .swift — previously missing, which meant RNPhotoEditor.swift was
|
|
23
|
+
# never compiled when consumers installed this pod. The ObjC bridge (.mm)
|
|
24
|
+
# references the Swift class via RCT_EXTERN_MODULE, so both must be compiled.
|
|
25
|
+
s.source_files = "**/*.{h,m,mm,swift}"
|
|
26
|
+
|
|
27
|
+
# External photo-editor SDK (Swift framework)
|
|
19
28
|
s.dependency "iOSPhotoEditor"
|
|
20
29
|
|
|
30
|
+
# Always enforce C++17 so the pod target can include C++ standard-library
|
|
31
|
+
# headers (<utility>, <vector>, <chrono>, …) pulled in transitively by
|
|
32
|
+
# React Native's TurboModule / JSI headers, regardless of RN version or
|
|
33
|
+
# architecture. Without this flag the compiler treats .mm files as plain
|
|
34
|
+
# Objective-C and fails with "'utility' file not found".
|
|
35
|
+
s.pod_target_xcconfig = {
|
|
36
|
+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
|
|
37
|
+
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# install_modules_dependencies wires up all New Architecture (TurboModule /
|
|
41
|
+
# Fabric) pod dependencies automatically on RN >= 0.71. On older RN it falls
|
|
42
|
+
# through to the else branch and adds React-Core manually.
|
|
21
43
|
if respond_to?(:install_modules_dependencies, true)
|
|
22
44
|
install_modules_dependencies(s)
|
|
23
45
|
else
|
|
24
46
|
s.dependency "React-Core"
|
|
47
|
+
|
|
48
|
+
if ENV["RCT_NEW_ARCH_ENABLED"] == "1"
|
|
49
|
+
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
|
50
|
+
s.dependency "React-Codegen"
|
|
51
|
+
s.dependency "RCT-Folly"
|
|
52
|
+
s.dependency "RCTRequired"
|
|
53
|
+
s.dependency "RCTTypeSafety"
|
|
54
|
+
s.dependency "ReactCommon/turbomodule/core"
|
|
55
|
+
end
|
|
25
56
|
end
|
|
26
57
|
end
|
package/ios/RNPhotoEditor.mm
CHANGED
|
@@ -4,11 +4,37 @@
|
|
|
4
4
|
#import <RNPhotoEditorSpec/RNPhotoEditorSpec.h>
|
|
5
5
|
#endif
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Objective-C++ bridge that exposes the Swift `RNPhotoEditor` class to
|
|
9
|
+
* React Native.
|
|
10
|
+
*
|
|
11
|
+
* Method selector change
|
|
12
|
+
* ──────────────────────
|
|
13
|
+
* The TurboModule codegen spec defines `edit(props: UnsafeObject): Promise<string>`.
|
|
14
|
+
* Codegen maps a Promise-returning method to two extra parameters on the native
|
|
15
|
+
* side — a resolve block and a reject block — using the naming convention:
|
|
16
|
+
*
|
|
17
|
+
* edit:(NSDictionary *)props
|
|
18
|
+
* resolve:(RCTPromiseResolveBlock)resolve
|
|
19
|
+
* reject:(RCTPromiseRejectBlock)reject
|
|
20
|
+
*
|
|
21
|
+
* The previous selector `Edit:onDone:onCancel:` used `RCTResponseSenderBlock`
|
|
22
|
+
* which is not recognised by the TurboModule codegen validator and is unsafe
|
|
23
|
+
* across JS context reloads.
|
|
24
|
+
*
|
|
25
|
+
* requiresMainQueueSetup / methodQueue
|
|
26
|
+
* ─────────────────────────────────────
|
|
27
|
+
* Returning YES from +requiresMainQueueSetup ensures the module is initialised
|
|
28
|
+
* on the main thread. The Swift side also overrides methodQueue to return
|
|
29
|
+
* DispatchQueue.main, so UI work happens synchronously without dispatch_async.
|
|
30
|
+
*/
|
|
7
31
|
@interface RCT_EXTERN_MODULE(RNPhotoEditor, NSObject)
|
|
8
32
|
|
|
9
|
-
RCT_EXTERN_METHOD(
|
|
10
|
-
|
|
11
|
-
|
|
33
|
+
RCT_EXTERN_METHOD(
|
|
34
|
+
edit:(NSDictionary *)props
|
|
35
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
36
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
37
|
+
)
|
|
12
38
|
|
|
13
39
|
+ (BOOL)requiresMainQueueSetup {
|
|
14
40
|
return YES;
|
|
@@ -17,9 +43,9 @@ RCT_EXTERN_METHOD(Edit:(NSDictionary *)props
|
|
|
17
43
|
@end
|
|
18
44
|
|
|
19
45
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
20
|
-
//
|
|
21
|
-
// The actual implementation is in RNPhotoEditor.swift; this
|
|
22
|
-
//
|
|
46
|
+
// Declare that the Swift class satisfies the codegen-generated protocol.
|
|
47
|
+
// The actual implementation is in RNPhotoEditor.swift; this forward
|
|
48
|
+
// declaration makes the compiler verify conformance at build time.
|
|
23
49
|
@interface RNPhotoEditor () <NativeRNPhotoEditorSpec>
|
|
24
50
|
@end
|
|
25
51
|
#endif
|