diff --git a/android/build.gradle b/android/build.gradle index 1f7d7b6..c94841e 100755 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,21 +1,30 @@ - buildscript { - repositories { - google() - mavenCentral() - } + repositories { + google() + mavenCentral() + } - dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' - } + dependencies { + classpath "com.android.tools.build:gradle:7.2.1" + } +} + +def isNewArchitectureEnabled() { + return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" +} + +apply plugin: "com.android.library" + +if (isNewArchitectureEnabled()) { + apply plugin: "com.facebook.react" } def getExtOrDefault(name) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['RNImageEditor_' + name] + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["RNImageEditor_" + name] } def getExtOrIntegerDefault(name) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['RNImageEditor_' + name]).toInteger() + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["RNImageEditor_" + name]).toInteger() } def supportsNamespace() { @@ -27,34 +36,60 @@ def supportsNamespace() { return (major == 7 && minor >= 3) || major >= 8 } - -apply plugin: 'com.android.library' - android { - compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') - buildToolsVersion getExtOrDefault('buildToolsVersion') + if (supportsNamespace()) { + namespace "com.reactnativecommunity.imageeditor" - if (supportsNamespace()) { - namespace "com.reactnativecommunity.imageeditor" + sourceSets { + main { + manifest.srcFile "src/main/AndroidManifestNew.xml" + } + } + } + compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") + defaultConfig { + minSdkVersion getExtOrIntegerDefault("minSdkVersion") + targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + } + buildFeatures { + buildConfig true + } - sourceSets { - main { - manifest.srcFile "src/main/AndroidManifestNew.xml" - } - } + buildTypes { + release { + minifyEnabled false } + } - defaultConfig { - minSdkVersion getExtOrIntegerDefault('minSdkVersion') - targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') + lintOptions { + disable "GradleCompatible" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + sourceSets { + main { + if (isNewArchitectureEnabled()) { + java.srcDirs += ["src/newarch", "${project.buildDir}/generated/source/codegen/java"] + } else { + java.srcDirs += ["src/oldarch"] + } } + } } repositories { - mavenCentral() + mavenCentral() + google() } dependencies { - api 'com.facebook.react:react-native:+' + // For < 0.71, this will be from the local maven repo + // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin + //noinspection GradleDynamicVersion + implementation "com.facebook.react:react-native:+" } - \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index a24577a..b66fc42 100755 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,3 @@ -RNImageEditor_compileSdkVersion=28 -RNImageEditor_buildToolsVersion=28.0.3 -RNImageEditor_targetSdkVersion=28 -RNImageEditor_minSdkVersion=19 \ No newline at end of file +RNImageEditor_compileSdkVersion=34 +RNImageEditor_targetSdkVersion=34 +RNImageEditor_minSdkVersion=21 diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 9a4163a..df31ce3 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Sat Nov 04 23:34:44 CET 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 8797405..28ddd50 100755 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,6 +1,3 @@ - - - \ No newline at end of file diff --git a/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModule.java b/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModuleImpl.java similarity index 88% rename from android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModule.java rename to android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModuleImpl.java index c053ab4..6ea4d32 100644 --- a/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModule.java +++ b/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModuleImpl.java @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ - package com.reactnativecommunity.imageeditor; import javax.annotation.Nullable; @@ -18,9 +17,7 @@ import java.net.URL; import java.net.URLConnection; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Map; import android.annotation.SuppressLint; import android.content.ContentResolver; @@ -43,23 +40,20 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableMap; import com.facebook.infer.annotation.Assertions; import com.facebook.react.common.ReactConstants; -/** - * Native module that provides image cropping functionality. - */ -public class ImageEditorModule extends ReactContextBaseJavaModule { +public class ImageEditorModuleImpl { + private ReactApplicationContext reactContext; protected static final String NAME = "RNCImageEditor"; private static final List LOCAL_URI_PREFIXES = Arrays.asList( - ContentResolver.SCHEME_FILE, - ContentResolver.SCHEME_CONTENT, - ContentResolver.SCHEME_ANDROID_RESOURCE + ContentResolver.SCHEME_FILE, + ContentResolver.SCHEME_CONTENT, + ContentResolver.SCHEME_ANDROID_RESOURCE ); private static final String TEMP_FILE_PREFIX = "ReactNative_cropped_image_"; @@ -95,24 +89,13 @@ public class ImageEditorModule extends ReactContextBaseJavaModule { ExifInterface.TAG_WHITE_BALANCE }; - public ImageEditorModule(ReactApplicationContext reactContext) { - super(reactContext); - new CleanTask(getReactApplicationContext()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @Override - public String getName() { - return NAME; - } - - @Override - public Map getConstants() { - return Collections.emptyMap(); + public ImageEditorModuleImpl(ReactApplicationContext context) { + reactContext = context; + new CleanTask(reactContext).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - @Override public void onCatalystInstanceDestroy() { - new CleanTask(getReactApplicationContext()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new CleanTask(reactContext).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } /** @@ -139,12 +122,12 @@ protected void doInBackgroundGuarded(Void... params) { private void cleanDirectory(File directory) { File[] toDelete = directory.listFiles( - new FilenameFilter() { - @Override - public boolean accept(File dir, String filename) { - return filename.startsWith(TEMP_FILE_PREFIX); - } - }); + new FilenameFilter() { + @Override + public boolean accept(File dir, String filename) { + return filename.startsWith(TEMP_FILE_PREFIX); + } + }); if (toDelete != null) { for (File file: toDelete) { file.delete(); @@ -166,16 +149,15 @@ public boolean accept(File dir, String filename) { * @param promise Promise to be resolved when the image has been cropped; the only argument that * is passed to this is the file:// URI of the new image */ - @ReactMethod public void cropImage( - String uri, - ReadableMap options, - Promise promise) { + String uri, + ReadableMap options, + Promise promise) { ReadableMap offset = options.hasKey("offset") ? options.getMap("offset") : null; ReadableMap size = options.hasKey("size") ? options.getMap("size") : null; if (offset == null || size == null || - !offset.hasKey("x") || !offset.hasKey("y") || - !size.hasKey("width") || !size.hasKey("height")) { + !offset.hasKey("x") || !offset.hasKey("y") || + !size.hasKey("width") || !size.hasKey("height")) { throw new JSApplicationIllegalArgumentException("Please specify offset and size"); } if (uri == null || uri.isEmpty()) { @@ -183,13 +165,13 @@ public void cropImage( } CropTask cropTask = new CropTask( - getReactApplicationContext(), - uri, - (int) offset.getDouble("x"), - (int) offset.getDouble("y"), - (int) size.getDouble("width"), - (int) size.getDouble("height"), - promise); + reactContext, + uri, + (int) offset.getDouble("x"), + (int) offset.getDouble("y"), + (int) size.getDouble("width"), + (int) size.getDouble("height"), + promise); if (options.hasKey("displaySize")) { ReadableMap targetSize = options.getMap("displaySize"); cropTask.setTargetSize( @@ -211,17 +193,17 @@ private static class CropTask extends GuardedAsyncTask { final Promise mPromise; private CropTask( - ReactContext context, - String uri, - int x, - int y, - int width, - int height, - Promise promise) { + ReactContext context, + String uri, + int x, + int y, + int width, + int height, + Promise promise) { super(context); if (x < 0 || y < 0 || width <= 0 || height <= 0) { throw new JSApplicationIllegalArgumentException(String.format( - "Invalid crop rectangle: [%d, %d, %d, %d]", x, y, width, height)); + "Invalid crop rectangle: [%d, %d, %d, %d]", x, y, width, height)); } mContext = context; mUri = uri; @@ -235,7 +217,7 @@ private CropTask( public void setTargetSize(int width, int height) { if (width <= 0 || height <= 0) { throw new JSApplicationIllegalArgumentException(String.format( - "Invalid target size: [%d, %d]", width, height)); + "Invalid target size: [%d, %d]", width, height)); } mTargetWidth = width; mTargetHeight = height; @@ -314,10 +296,10 @@ private Bitmap crop(BitmapFactory.Options outOptions) throws IOException { * @param outOptions Bitmap options, useful to determine {@code outMimeType}. */ private Bitmap cropAndResize( - int targetWidth, - int targetHeight, - BitmapFactory.Options outOptions) - throws IOException { + int targetWidth, + int targetHeight, + BitmapFactory.Options outOptions) + throws IOException { Assertions.assertNotNull(outOptions); // Loading large bitmaps efficiently: @@ -450,7 +432,7 @@ private static Bitmap.CompressFormat getCompressFormatForType(String type) { } private static void writeCompressedBitmapToFile(Bitmap cropped, String mimeType, File tempFile) - throws IOException { + throws IOException { OutputStream out = new FileOutputStream(tempFile); try { cropped.compress(getCompressFormatForType(mimeType), COMPRESS_QUALITY, out); @@ -468,7 +450,7 @@ private static void writeCompressedBitmapToFile(Bitmap cropped, String mimeType, * @param mimeType the MIME type of the file to create (image/*) */ private static File createTempFile(Context context, @Nullable String mimeType) - throws IOException { + throws IOException { File externalCacheDir = context.getExternalCacheDir(); File internalCacheDir = context.getCacheDir(); File cacheDir; @@ -482,7 +464,7 @@ else if (internalCacheDir == null) { cacheDir = externalCacheDir; } else { cacheDir = externalCacheDir.getFreeSpace() > internalCacheDir.getFreeSpace() ? - externalCacheDir : internalCacheDir; + externalCacheDir : internalCacheDir; } return File.createTempFile(TEMP_FILE_PREFIX, getFileExtensionForType(mimeType), cacheDir); } @@ -499,7 +481,7 @@ private static int getDecodeSampleSize(int width, int height, int targetWidth, i int halfHeight = height / 2; int halfWidth = width / 2; while ((halfWidth / inSampleSize) >= targetWidth - && (halfHeight / inSampleSize) >= targetHeight) { + && (halfHeight / inSampleSize) >= targetHeight) { inSampleSize *= 2; } } diff --git a/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorPackage.java b/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorPackage.java index a2b51ea..334447e 100644 --- a/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorPackage.java +++ b/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorPackage.java @@ -7,30 +7,59 @@ package com.reactnativecommunity.imageeditor; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import androidx.annotation.Nullable; -import com.facebook.react.ReactPackage; +import com.facebook.react.TurboReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.module.model.ReactModuleInfo; +import com.facebook.react.module.model.ReactModuleInfoProvider; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; -public class ImageEditorPackage implements ReactPackage { - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(new ImageEditorModule(reactContext)); - } +import java.util.HashMap; +import java.util.Map; + +public class ImageEditorPackage extends TurboReactPackage { - // Deprecated from RN 0.47 - public List> createJSModules() { - return Collections.emptyList(); + @Nullable + @Override + public NativeModule getModule(String name, ReactApplicationContext reactContext) { + if (name.equals(ImageEditorModule.NAME)) { + return new ImageEditorModule(reactContext); + } else { + return null; } + } - @Override - @SuppressWarnings("rawtypes") - public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); + @Override + public ReactModuleInfoProvider getReactModuleInfoProvider() { + Class[] moduleList = new Class[] { + ImageEditorModule.class + }; + final Map reactModuleInfoMap = new HashMap<>(); + + for (Class moduleClass : moduleList) { + ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); + reactModuleInfoMap.put( + reactModule.name(), + new ReactModuleInfo( + reactModule.name(), + moduleClass.getName(), + true, + reactModule.needsEagerInit(), + reactModule.hasConstants(), + reactModule.isCxxModule(), + TurboModule.class.isAssignableFrom(moduleClass) + ) + ); } -} \ No newline at end of file + + return new ReactModuleInfoProvider() { + @Override + public Map getReactModuleInfos() { + return reactModuleInfoMap; + } + }; + } +} diff --git a/android/src/newarch/com/reactnativecommunity/imageeditor/ImageEditorModule.java b/android/src/newarch/com/reactnativecommunity/imageeditor/ImageEditorModule.java new file mode 100644 index 0000000..5fc0fe6 --- /dev/null +++ b/android/src/newarch/com/reactnativecommunity/imageeditor/ImageEditorModule.java @@ -0,0 +1,36 @@ +package com.reactnativecommunity.imageeditor; + +import androidx.annotation.NonNull; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.annotations.ReactModule; + +@ReactModule(name = ImageEditorModule.NAME) +public class ImageEditorModule extends NativeRNCImageEditorSpec { + private ImageEditorModuleImpl moduleImpl; + + ImageEditorModule(ReactApplicationContext context) { + super(context); + moduleImpl = new ImageEditorModuleImpl(context); + } + + public static final String NAME = ImageEditorModuleImpl.NAME; + + @Override + @NonNull + public String getName() { + return ImageEditorModuleImpl.NAME; + } + + @Override + public void onCatalystInstanceDestroy() { + moduleImpl.onCatalystInstanceDestroy(); + } + + @Override + public void cropImage(String uri, ReadableMap cropData, Promise promise) { + moduleImpl.cropImage(uri, cropData, promise); + } +} diff --git a/android/src/oldarch/com/reactnativecommunity/imageeditor/ImageEditorModule.java b/android/src/oldarch/com/reactnativecommunity/imageeditor/ImageEditorModule.java new file mode 100644 index 0000000..fbe518f --- /dev/null +++ b/android/src/oldarch/com/reactnativecommunity/imageeditor/ImageEditorModule.java @@ -0,0 +1,44 @@ +package com.reactnativecommunity.imageeditor; + +import java.util.Collections; +import java.util.Map; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.module.annotations.ReactModule; + +@ReactModule(name = ImageEditorModule.NAME) +public class ImageEditorModule extends ReactContextBaseJavaModule { + private ImageEditorModuleImpl moduleImpl; + + public ImageEditorModule(ReactApplicationContext reactContext) { + super(reactContext); + moduleImpl = new ImageEditorModuleImpl(reactContext); + } + + public static final String NAME = ImageEditorModuleImpl.NAME; + + @Override + public String getName() { + return ImageEditorModuleImpl.NAME; + } + + + @Override + public Map getConstants() { + return Collections.emptyMap(); + } + + @Override + public void onCatalystInstanceDestroy() { + moduleImpl.onCatalystInstanceDestroy(); + } + + @ReactMethod + public void cropImage(String uri, ReadableMap options, Promise promise) { + moduleImpl.cropImage(uri, options, promise); + } +} diff --git a/ios/RNCFileSystem.m b/ios/RNCFileSystem.mm similarity index 100% rename from ios/RNCFileSystem.m rename to ios/RNCFileSystem.mm diff --git a/ios/RNCImageEditor.h b/ios/RNCImageEditor.h index 2fb37e8..fe094ab 100644 --- a/ios/RNCImageEditor.h +++ b/ios/RNCImageEditor.h @@ -5,8 +5,14 @@ * LICENSE file in the root directory of this source tree. */ +#ifdef RCT_NEW_ARCH_ENABLED +#import "RNCImageEditorSpec.h" + +@interface RNCImageEditor : NSObject +#else #import @interface RNCImageEditor : NSObject +#endif @end diff --git a/ios/RNCImageEditor.m b/ios/RNCImageEditor.mm similarity index 66% rename from ios/RNCImageEditor.m rename to ios/RNCImageEditor.mm index 7c2bede..749a354 100644 --- a/ios/RNCImageEditor.m +++ b/ios/RNCImageEditor.mm @@ -40,15 +40,37 @@ @implementation RNCImageEditor * be scaled down to `displaySize` rather than `size`. * All units are in px (not points). */ +#ifdef RCT_NEW_ARCH_ENABLED +- (void) cropImage:(NSString *)uri + cropData:(JS::NativeRNCImageEditor::SpecCropImageCropData &)data + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject +{ + CGSize size = [RCTConvert CGSize:@{ @"width": @(data.size().width()), @"height": @(data.size().height()) }]; + CGPoint offset = [RCTConvert CGPoint:@{ @"x": @(data.offset().x()), @"y": @(data.offset().y()) }]; + CGSize targetSize = size; + if (data.displaySize().has_value()) { + JS::NativeRNCImageEditor::SpecCropImageCropDataDisplaySize displaySize = *data.displaySize(); // Extract the value from the optional + // in pixels + targetSize = [RCTConvert CGSize:@{ @"width": @(displaySize.width()), @"height": @(displaySize.height()) }]; + } + NSString *displaySize = data.resizeMode(); + NSURLRequest *imageRequest = [NSURLRequest requestWithURL:[NSURL URLWithString: uri]]; +#else RCT_EXPORT_METHOD(cropImage:(NSURLRequest *)imageRequest cropData:(NSDictionary *)cropData resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - CGRect rect = { - [RCTConvert CGPoint:cropData[@"offset"]], - [RCTConvert CGSize:cropData[@"size"]] - }; + CGSize size = [RCTConvert CGSize:cropData[@"size"]]; + CGPoint offset = [RCTConvert CGPoint:cropData[@"offset"]]; + CGSize targetSize = size; + NSString *displaySize = cropData[@"resizeMode"]; + if(displaySize){ + targetSize = [RCTConvert CGSize:cropData[@"displaySize"]]; + } +#endif + CGRect rect = {offset,size}; NSURL *url = [imageRequest URL]; NSString *urlPath = [url path]; NSString *extension = [urlPath pathExtension]; @@ -60,15 +82,13 @@ @implementation RNCImageEditor } // Crop image - CGSize targetSize = rect.size; CGRect targetRect = {{-rect.origin.x, -rect.origin.y}, image.size}; CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetRect); UIImage *croppedImage = RCTTransformImage(image, targetSize, image.scale, transform); // Scale image - if (cropData[@"displaySize"]) { - targetSize = [RCTConvert CGSize:cropData[@"displaySize"]]; // in pixels - RCTResizeMode resizeMode = [RCTConvert RCTResizeMode:cropData[@"resizeMode"] ?: @"contain"]; + if (displaySize) { + RCTResizeMode resizeMode = [RCTConvert RCTResizeMode:displaySize ?: @"contain"]; targetRect = RCTTargetRect(croppedImage.size, targetSize, 1, resizeMode); transform = RCTTransformFromTargetRect(croppedImage.size, targetRect); croppedImage = RCTTransformImage(croppedImage, targetSize, image.scale, transform); @@ -77,7 +97,7 @@ @implementation RNCImageEditor // Store image NSString *path = NULL; NSData *imageData = NULL; - + if([extension isEqualToString:@"png"]){ imageData = UIImagePNGRepresentation(croppedImage); path = [RNCFileSystem generatePathInDirectory:[[RNCFileSystem cacheDirectoryPath] stringByAppendingPathComponent:@"ReactNative_cropped_image_"] withExtension:@".png"]; @@ -90,14 +110,22 @@ @implementation RNCImageEditor NSError *writeError; NSString *uri = [RNCImageUtils writeImage:imageData toPath:path error:&writeError]; - + if (writeError != nil) { reject(@(writeError.code).stringValue, writeError.description, writeError); return; } - + resolve(uri); }]; } +#ifdef RCT_NEW_ARCH_ENABLED +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} +#endif + @end diff --git a/ios/RNCImageEditor.xcodeproj/project.pbxproj b/ios/RNCImageEditor.xcodeproj/project.pbxproj deleted file mode 100755 index 7433613..0000000 --- a/ios/RNCImageEditor.xcodeproj/project.pbxproj +++ /dev/null @@ -1,273 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1478DE2B22A0403F00D818FA /* RNCFileSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1478DE2A22A0403F00D818FA /* RNCFileSystem.m */; }; - 1478DE2E22A044E500D818FA /* RNCImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1478DE2D22A044E500D818FA /* RNCImageUtils.m */; }; - B3E7B58A1CC2AC0600A0062D /* RNCImageEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNCImageEditor.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 58B511D91A9E6C8500147676 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; - dstSubfolderSpec = 16; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 134814201AA4EA6300B7C361 /* libRNCImageEditor.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCImageEditor.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 1478DE2A22A0403F00D818FA /* RNCFileSystem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNCFileSystem.m; sourceTree = ""; }; - 1478DE2C22A0406800D818FA /* RNCFileSystem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNCFileSystem.h; sourceTree = ""; }; - 1478DE2D22A044E500D818FA /* RNCImageUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNCImageUtils.m; sourceTree = ""; }; - 1478DE2F22A0450800D818FA /* RNCImageUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNCImageUtils.h; sourceTree = ""; }; - B3E7B5881CC2AC0600A0062D /* RNCImageEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCImageEditor.h; sourceTree = ""; }; - B3E7B5891CC2AC0600A0062D /* RNCImageEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCImageEditor.m; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 58B511D81A9E6C8500147676 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 134814211AA4EA7D00B7C361 /* Products */ = { - isa = PBXGroup; - children = ( - 134814201AA4EA6300B7C361 /* libRNCImageEditor.a */, - ); - name = Products; - sourceTree = ""; - }; - 58B511D21A9E6C8500147676 = { - isa = PBXGroup; - children = ( - 1478DE2A22A0403F00D818FA /* RNCFileSystem.m */, - 1478DE2D22A044E500D818FA /* RNCImageUtils.m */, - 1478DE2F22A0450800D818FA /* RNCImageUtils.h */, - 1478DE2C22A0406800D818FA /* RNCFileSystem.h */, - B3E7B5881CC2AC0600A0062D /* RNCImageEditor.h */, - B3E7B5891CC2AC0600A0062D /* RNCImageEditor.m */, - 134814211AA4EA7D00B7C361 /* Products */, - ); - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 58B511DA1A9E6C8500147676 /* RNCImageEditor */ = { - isa = PBXNativeTarget; - buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCImageEditor" */; - buildPhases = ( - 58B511D71A9E6C8500147676 /* Sources */, - 58B511D81A9E6C8500147676 /* Frameworks */, - 58B511D91A9E6C8500147676 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = RNCImageEditor; - productName = RCTDataManager; - productReference = 134814201AA4EA6300B7C361 /* libRNCImageEditor.a */; - productType = "com.apple.product-type.library.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 58B511D31A9E6C8500147676 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0830; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 58B511DA1A9E6C8500147676 = { - CreatedOnToolsVersion = 6.1.1; - }; - }; - }; - buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCImageEditor" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 58B511D21A9E6C8500147676; - productRefGroup = 58B511D21A9E6C8500147676; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 58B511DA1A9E6C8500147676 /* RNCImageEditor */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 58B511D71A9E6C8500147676 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1478DE2E22A044E500D818FA /* RNCImageUtils.m in Sources */, - 1478DE2B22A0403F00D818FA /* RNCFileSystem.m in Sources */, - B3E7B58A1CC2AC0600A0062D /* RNCImageEditor.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 58B511ED1A9E6C8500147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 58B511EE1A9E6C8500147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 58B511F01A9E6C8500147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../../React/**", - "$(SRCROOT)/../../react-native/React/**", - "$(SRCROOT)/../../../react-native/Libraries/Image/**", - ); - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = RNCImageEditor; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 58B511F11A9E6C8500147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../../React/**", - "$(SRCROOT)/../../react-native/React/**", - "$(SRCROOT)/../../../react-native/Libraries/Image/**", - ); - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = RNCImageEditor; - SKIP_INSTALL = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCImageEditor" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511ED1A9E6C8500147676 /* Debug */, - 58B511EE1A9E6C8500147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCImageEditor" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511F01A9E6C8500147676 /* Debug */, - 58B511F11A9E6C8500147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 58B511D31A9E6C8500147676 /* Project object */; -} diff --git a/ios/RNCImageUtils.m b/ios/RNCImageUtils.mm similarity index 100% rename from ios/RNCImageUtils.m rename to ios/RNCImageUtils.mm diff --git a/package.json b/package.json index 8767026..0558d5b 100755 --- a/package.json +++ b/package.json @@ -83,6 +83,14 @@ "access": "public", "registry": "https://registry.npmjs.org/" }, + "codegenConfig": { + "name": "RNCImageEditorSpec", + "type": "modules", + "jsSrcsDir": "src", + "android": { + "javaPackageName": "com.reactnativecommunity.imageeditor" + } + }, "react-native-builder-bob": { "source": "src", "output": "lib", diff --git a/react-native-image-editor.podspec b/react-native-image-editor.podspec index 6cf4969..0f18b21 100644 --- a/react-native-image-editor.podspec +++ b/react-native-image-editor.podspec @@ -1,6 +1,7 @@ require 'json' package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' Pod::Spec.new do |s| s.name = "react-native-image-editor" @@ -13,8 +14,30 @@ Pod::Spec.new do |s| s.platform = :ios, "9.0" s.source = { :git => "https://github.com/callstack/react-native-image-editor.git", :tag => "#{s.version}" } - s.source_files = "ios/**/*.{h,m}" + s.source_files = "ios/**/*.{h,m,mm}" - s.dependency 'React' s.dependency 'React-RCTImage' + + # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. + # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + + # Don't install the dependencies when we run `pod install` in the old architecture. + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + s.dependency "React-Codegen" + s.dependency "RCT-Folly" + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" + end + end end diff --git a/src/NativeRNCImageEditor.ts b/src/NativeRNCImageEditor.ts new file mode 100644 index 0000000..5d7b21c --- /dev/null +++ b/src/NativeRNCImageEditor.ts @@ -0,0 +1,42 @@ +import type { TurboModule } from 'react-native'; +import type { Double } from 'react-native/Libraries/Types/CodegenTypes'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + cropImage( + uri: string, + cropData: { + // inlined `ImageCropData` type (for older RN versions 71 and below) + /** + * The top-left corner of the cropped image, specified in the original + * image's coordinate space. + */ + offset: { + x: Double; + y: Double; + }; + /** + * The size (dimensions) of the cropped image, specified in the original + * image's coordinate space. + */ + size: { + width: Double; + height: Double; + }; + /** + * (Optional) size to scale the cropped image to. + */ + displaySize?: { + width: Double; + height: Double; + }; + /** + * (Optional) the resizing mode to use when scaling the image. If the + * `displaySize` param is not specified, this has no effect. + */ + resizeMode?: string; + } + ): Promise; +} + +export default TurboModuleRegistry.getEnforcing('RNCImageEditor'); diff --git a/src/index.ts b/src/index.ts index 5bc574b..db97f24 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,42 +5,31 @@ * LICENSE file in the root directory of this source tree. * */ +import { Platform } from 'react-native'; +import NativeRNCImageEditor from './NativeRNCImageEditor'; +import type { Spec } from './NativeRNCImageEditor'; -import { NativeModules } from 'react-native'; +const LINKING_ERROR = + `The package '@react-native-community/image-editor' doesn't seem to be linked. Make sure: \n\n` + + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + + '- You rebuilt the app after installing the package\n' + + '- You are not using Expo Go\n'; -const { RNCImageEditor } = NativeModules; +const RNCImageEditor: Spec = NativeRNCImageEditor + ? NativeRNCImageEditor + : new Proxy({} as Spec, { + get() { + throw new Error(LINKING_ERROR); + }, + }); -type $Maybe = T | null | undefined; +type ImageCropDataFromSpec = Parameters[1]; -export interface ImageCropData { - /** - * The top-left corner of the cropped image, specified in the original - * image's coordinate space. - */ - offset: { - x: number; - y: number; - }; - /** - * The size (dimensions) of the cropped image, specified in the original - * image's coordinate space. - */ - size: { - width: number; - height: number; - }; - /** - * (Optional) size to scale the cropped image to. - */ - displaySize?: $Maybe<{ - width: number; - height: number; - }>; - /** - * (Optional) the resizing mode to use when scaling the image. If the - * `displaySize` param is not specified, this has no effect. - */ - resizeMode?: $Maybe<'contain' | 'cover' | 'stretch'>; +export interface ImageCropData + extends Pick { + resizeMode?: 'contain' | 'cover' | 'stretch'; + // ^^^ codegen doesn't support union types yet + // so to provide more type safety we override the type here } class ImageEditor {