From 2f10f5b90b1502c5bc4461547f9063d2b95f4492 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Mon, 9 Feb 2026 05:28:06 +0800 Subject: [PATCH 1/4] chore: lint and format fixes --- .eslintignore | 7 - .../java/com/lodev09/exify/ExifyPackage.kt | 30 +- .../main/java/com/lodev09/exify/ExifyTags.kt | 271 +++++++++--------- .../main/java/com/lodev09/exify/ExifyUtils.kt | 15 +- eslint.config.mjs | 11 +- ios/Exify.h | 2 +- ios/Exify.mm | 189 +++++++----- scripts/ktlint.sh | 2 +- 8 files changed, 292 insertions(+), 235 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 443639f..0000000 --- a/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -coverage -dist -ios -android -.vscode -.expo diff --git a/android/src/main/java/com/lodev09/exify/ExifyPackage.kt b/android/src/main/java/com/lodev09/exify/ExifyPackage.kt index e0afd2b..c12de03 100644 --- a/android/src/main/java/com/lodev09/exify/ExifyPackage.kt +++ b/android/src/main/java/com/lodev09/exify/ExifyPackage.kt @@ -8,26 +8,28 @@ import com.facebook.react.module.model.ReactModuleInfoProvider import java.util.HashMap class ExifyPackage : BaseReactPackage() { - override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { - return if (name == ExifyModule.NAME) { + override fun getModule( + name: String, + reactContext: ReactApplicationContext, + ): NativeModule? = + if (name == ExifyModule.NAME) { ExifyModule(reactContext) } else { null } - } - override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { - return ReactModuleInfoProvider { + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = + ReactModuleInfoProvider { val moduleInfos: MutableMap = HashMap() - moduleInfos[ExifyModule.NAME] = ReactModuleInfo( - ExifyModule.NAME, - ExifyModule.NAME, - false, // canOverrideExistingModule - false, // needsEagerInit - false, // isCxxModule - true // isTurboModule - ) + moduleInfos[ExifyModule.NAME] = + ReactModuleInfo( + ExifyModule.NAME, + ExifyModule.NAME, + false, // canOverrideExistingModule + false, // needsEagerInit + false, // isCxxModule + true, // isTurboModule + ) moduleInfos } - } } diff --git a/android/src/main/java/com/lodev09/exify/ExifyTags.kt b/android/src/main/java/com/lodev09/exify/ExifyTags.kt index c88fa54..5e6eaed 100644 --- a/android/src/main/java/com/lodev09/exify/ExifyTags.kt +++ b/android/src/main/java/com/lodev09/exify/ExifyTags.kt @@ -6,138 +6,139 @@ import androidx.exifinterface.media.ExifInterface * Supported Exif Tags * Note: Latitude, Longitude and Altitude tags are updated separately */ -val EXIFY_TAGS = arrayOf( - arrayOf("string", ExifInterface.TAG_ARTIST), - arrayOf("int", ExifInterface.TAG_BITS_PER_SAMPLE), - arrayOf("int", ExifInterface.TAG_COMPRESSION), - arrayOf("string", ExifInterface.TAG_COPYRIGHT), - arrayOf("string", ExifInterface.TAG_DATETIME), - arrayOf("string", ExifInterface.TAG_IMAGE_DESCRIPTION), - arrayOf("int", ExifInterface.TAG_IMAGE_LENGTH), - arrayOf("int", ExifInterface.TAG_IMAGE_WIDTH), - arrayOf("int", ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT), - arrayOf("int", ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH), - arrayOf("string", ExifInterface.TAG_MAKE), - arrayOf("string", ExifInterface.TAG_MODEL), - arrayOf("int", ExifInterface.TAG_ORIENTATION), - arrayOf("int", ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION), - arrayOf("int", ExifInterface.TAG_PLANAR_CONFIGURATION), - arrayOf("double", ExifInterface.TAG_PRIMARY_CHROMATICITIES), - arrayOf("double", ExifInterface.TAG_REFERENCE_BLACK_WHITE), - arrayOf("int", ExifInterface.TAG_RESOLUTION_UNIT), - arrayOf("int", ExifInterface.TAG_ROWS_PER_STRIP), - arrayOf("int", ExifInterface.TAG_SAMPLES_PER_PIXEL), - arrayOf("string", ExifInterface.TAG_SOFTWARE), - arrayOf("int", ExifInterface.TAG_STRIP_BYTE_COUNTS), - arrayOf("int", ExifInterface.TAG_STRIP_OFFSETS), - arrayOf("int", ExifInterface.TAG_TRANSFER_FUNCTION), - arrayOf("double", ExifInterface.TAG_WHITE_POINT), - arrayOf("double", ExifInterface.TAG_X_RESOLUTION), - arrayOf("double", ExifInterface.TAG_Y_CB_CR_COEFFICIENTS), - arrayOf("int", ExifInterface.TAG_Y_CB_CR_POSITIONING), - arrayOf("int", ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING), - arrayOf("double", ExifInterface.TAG_Y_RESOLUTION), - arrayOf("double", ExifInterface.TAG_APERTURE_VALUE), - arrayOf("double", ExifInterface.TAG_BRIGHTNESS_VALUE), - arrayOf("string", ExifInterface.TAG_CFA_PATTERN), - arrayOf("int", ExifInterface.TAG_COLOR_SPACE), - arrayOf("array", ExifInterface.TAG_COMPONENTS_CONFIGURATION), - arrayOf("double", ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL), - arrayOf("int", ExifInterface.TAG_CONTRAST), - arrayOf("int", ExifInterface.TAG_CUSTOM_RENDERED), - arrayOf("string", ExifInterface.TAG_DATETIME_DIGITIZED), - arrayOf("string", ExifInterface.TAG_DATETIME_ORIGINAL), - arrayOf("string", ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION), - arrayOf("double", ExifInterface.TAG_DIGITAL_ZOOM_RATIO), - arrayOf("array", ExifInterface.TAG_EXIF_VERSION), - arrayOf("double", ExifInterface.TAG_EXPOSURE_BIAS_VALUE), - arrayOf("double", ExifInterface.TAG_EXPOSURE_INDEX), - arrayOf("int", ExifInterface.TAG_EXPOSURE_MODE), - arrayOf("int", ExifInterface.TAG_EXPOSURE_PROGRAM), - arrayOf("double", ExifInterface.TAG_EXPOSURE_TIME), - arrayOf("double", ExifInterface.TAG_F_NUMBER), - arrayOf("string", ExifInterface.TAG_FILE_SOURCE), - arrayOf("int", ExifInterface.TAG_FLASH), - arrayOf("double", ExifInterface.TAG_FLASH_ENERGY), - arrayOf("array", ExifInterface.TAG_FLASHPIX_VERSION), - arrayOf("double", ExifInterface.TAG_FOCAL_LENGTH), - arrayOf("string", ExifInterface.TAG_LENS_MAKE), - arrayOf("string", ExifInterface.TAG_LENS_MODEL), - arrayOf("array", ExifInterface.TAG_LENS_SPECIFICATION), - arrayOf("int", ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM), - arrayOf("int", ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT), - arrayOf("double", ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION), - arrayOf("double", ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION), - arrayOf("int", ExifInterface.TAG_GAIN_CONTROL), - arrayOf("string", ExifInterface.TAG_ISO_SPEED_RATINGS), - arrayOf("string", ExifInterface.TAG_IMAGE_UNIQUE_ID), - arrayOf("int", ExifInterface.TAG_LIGHT_SOURCE), - arrayOf("string", ExifInterface.TAG_MAKER_NOTE), - arrayOf("double", ExifInterface.TAG_MAX_APERTURE_VALUE), - arrayOf("int", ExifInterface.TAG_METERING_MODE), - arrayOf("int", ExifInterface.TAG_NEW_SUBFILE_TYPE), - arrayOf("string", ExifInterface.TAG_OECF), - arrayOf("int", ExifInterface.TAG_PIXEL_X_DIMENSION), - arrayOf("int", ExifInterface.TAG_PIXEL_Y_DIMENSION), - arrayOf("string", ExifInterface.TAG_RELATED_SOUND_FILE), - arrayOf("int", ExifInterface.TAG_SATURATION), - arrayOf("int", ExifInterface.TAG_SCENE_CAPTURE_TYPE), - arrayOf("string", ExifInterface.TAG_SCENE_TYPE), - arrayOf("int", ExifInterface.TAG_SENSING_METHOD), - arrayOf("int", ExifInterface.TAG_SHARPNESS), - arrayOf("double", ExifInterface.TAG_SHUTTER_SPEED_VALUE), - arrayOf("string", ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE), - arrayOf("string", ExifInterface.TAG_SPECTRAL_SENSITIVITY), - arrayOf("int", ExifInterface.TAG_SUBFILE_TYPE), - arrayOf("string", ExifInterface.TAG_SUBSEC_TIME), - arrayOf("string", ExifInterface.TAG_SUBSEC_TIME_DIGITIZED), - arrayOf("string", ExifInterface.TAG_SUBSEC_TIME_ORIGINAL), - arrayOf("array", ExifInterface.TAG_SUBJECT_AREA), - arrayOf("double", ExifInterface.TAG_SUBJECT_DISTANCE), - arrayOf("int", ExifInterface.TAG_SUBJECT_DISTANCE_RANGE), - arrayOf("int", ExifInterface.TAG_SUBJECT_LOCATION), - arrayOf("string", ExifInterface.TAG_USER_COMMENT), - arrayOf("int", ExifInterface.TAG_WHITE_BALANCE), - arrayOf("int", ExifInterface.TAG_GPS_ALTITUDE_REF), - arrayOf("string", ExifInterface.TAG_GPS_AREA_INFORMATION), - arrayOf("double", ExifInterface.TAG_GPS_DOP), - arrayOf("string", ExifInterface.TAG_GPS_DATESTAMP), - arrayOf("double", ExifInterface.TAG_GPS_DEST_BEARING), - arrayOf("string", ExifInterface.TAG_GPS_DEST_BEARING_REF), - arrayOf("double", ExifInterface.TAG_GPS_DEST_DISTANCE), - arrayOf("string", ExifInterface.TAG_GPS_DEST_DISTANCE_REF), - arrayOf("double", ExifInterface.TAG_GPS_DEST_LATITUDE), - arrayOf("string", ExifInterface.TAG_GPS_DEST_LATITUDE_REF), - arrayOf("double", ExifInterface.TAG_GPS_DEST_LONGITUDE), - arrayOf("string", ExifInterface.TAG_GPS_DEST_LONGITUDE_REF), - arrayOf("int", ExifInterface.TAG_GPS_DIFFERENTIAL), - arrayOf("string", ExifInterface.TAG_GPS_H_POSITIONING_ERROR), - arrayOf("double", ExifInterface.TAG_GPS_IMG_DIRECTION), - arrayOf("string", ExifInterface.TAG_GPS_IMG_DIRECTION_REF), - arrayOf("string", ExifInterface.TAG_GPS_LATITUDE_REF), - arrayOf("string", ExifInterface.TAG_GPS_LONGITUDE_REF), - arrayOf("string", ExifInterface.TAG_GPS_MAP_DATUM), - arrayOf("string", ExifInterface.TAG_GPS_MEASURE_MODE), - arrayOf("string", ExifInterface.TAG_GPS_PROCESSING_METHOD), - arrayOf("string", ExifInterface.TAG_GPS_SATELLITES), - arrayOf("double", ExifInterface.TAG_GPS_SPEED), - arrayOf("string", ExifInterface.TAG_GPS_SPEED_REF), - arrayOf("string", ExifInterface.TAG_GPS_STATUS), - arrayOf("string", ExifInterface.TAG_GPS_TIMESTAMP), - arrayOf("double", ExifInterface.TAG_GPS_TRACK), - arrayOf("string", ExifInterface.TAG_GPS_TRACK_REF), - arrayOf("string", ExifInterface.TAG_GPS_VERSION_ID), - arrayOf("string", ExifInterface.TAG_INTEROPERABILITY_INDEX), - arrayOf("int", ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH), - arrayOf("int", ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH), - arrayOf("int", ExifInterface.TAG_DNG_VERSION), - arrayOf("int", ExifInterface.TAG_DEFAULT_CROP_SIZE), - arrayOf("int", ExifInterface.TAG_ORF_PREVIEW_IMAGE_START), - arrayOf("int", ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH), - arrayOf("int", ExifInterface.TAG_ORF_ASPECT_FRAME), - arrayOf("int", ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER), - arrayOf("int", ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER), - arrayOf("int", ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER), - arrayOf("int", ExifInterface.TAG_RW2_SENSOR_TOP_BORDER), - arrayOf("int", ExifInterface.TAG_RW2_ISO) -) +val EXIFY_TAGS = + arrayOf( + arrayOf("string", ExifInterface.TAG_ARTIST), + arrayOf("int", ExifInterface.TAG_BITS_PER_SAMPLE), + arrayOf("int", ExifInterface.TAG_COMPRESSION), + arrayOf("string", ExifInterface.TAG_COPYRIGHT), + arrayOf("string", ExifInterface.TAG_DATETIME), + arrayOf("string", ExifInterface.TAG_IMAGE_DESCRIPTION), + arrayOf("int", ExifInterface.TAG_IMAGE_LENGTH), + arrayOf("int", ExifInterface.TAG_IMAGE_WIDTH), + arrayOf("int", ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT), + arrayOf("int", ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH), + arrayOf("string", ExifInterface.TAG_MAKE), + arrayOf("string", ExifInterface.TAG_MODEL), + arrayOf("int", ExifInterface.TAG_ORIENTATION), + arrayOf("int", ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION), + arrayOf("int", ExifInterface.TAG_PLANAR_CONFIGURATION), + arrayOf("double", ExifInterface.TAG_PRIMARY_CHROMATICITIES), + arrayOf("double", ExifInterface.TAG_REFERENCE_BLACK_WHITE), + arrayOf("int", ExifInterface.TAG_RESOLUTION_UNIT), + arrayOf("int", ExifInterface.TAG_ROWS_PER_STRIP), + arrayOf("int", ExifInterface.TAG_SAMPLES_PER_PIXEL), + arrayOf("string", ExifInterface.TAG_SOFTWARE), + arrayOf("int", ExifInterface.TAG_STRIP_BYTE_COUNTS), + arrayOf("int", ExifInterface.TAG_STRIP_OFFSETS), + arrayOf("int", ExifInterface.TAG_TRANSFER_FUNCTION), + arrayOf("double", ExifInterface.TAG_WHITE_POINT), + arrayOf("double", ExifInterface.TAG_X_RESOLUTION), + arrayOf("double", ExifInterface.TAG_Y_CB_CR_COEFFICIENTS), + arrayOf("int", ExifInterface.TAG_Y_CB_CR_POSITIONING), + arrayOf("int", ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING), + arrayOf("double", ExifInterface.TAG_Y_RESOLUTION), + arrayOf("double", ExifInterface.TAG_APERTURE_VALUE), + arrayOf("double", ExifInterface.TAG_BRIGHTNESS_VALUE), + arrayOf("string", ExifInterface.TAG_CFA_PATTERN), + arrayOf("int", ExifInterface.TAG_COLOR_SPACE), + arrayOf("array", ExifInterface.TAG_COMPONENTS_CONFIGURATION), + arrayOf("double", ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL), + arrayOf("int", ExifInterface.TAG_CONTRAST), + arrayOf("int", ExifInterface.TAG_CUSTOM_RENDERED), + arrayOf("string", ExifInterface.TAG_DATETIME_DIGITIZED), + arrayOf("string", ExifInterface.TAG_DATETIME_ORIGINAL), + arrayOf("string", ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION), + arrayOf("double", ExifInterface.TAG_DIGITAL_ZOOM_RATIO), + arrayOf("array", ExifInterface.TAG_EXIF_VERSION), + arrayOf("double", ExifInterface.TAG_EXPOSURE_BIAS_VALUE), + arrayOf("double", ExifInterface.TAG_EXPOSURE_INDEX), + arrayOf("int", ExifInterface.TAG_EXPOSURE_MODE), + arrayOf("int", ExifInterface.TAG_EXPOSURE_PROGRAM), + arrayOf("double", ExifInterface.TAG_EXPOSURE_TIME), + arrayOf("double", ExifInterface.TAG_F_NUMBER), + arrayOf("string", ExifInterface.TAG_FILE_SOURCE), + arrayOf("int", ExifInterface.TAG_FLASH), + arrayOf("double", ExifInterface.TAG_FLASH_ENERGY), + arrayOf("array", ExifInterface.TAG_FLASHPIX_VERSION), + arrayOf("double", ExifInterface.TAG_FOCAL_LENGTH), + arrayOf("string", ExifInterface.TAG_LENS_MAKE), + arrayOf("string", ExifInterface.TAG_LENS_MODEL), + arrayOf("array", ExifInterface.TAG_LENS_SPECIFICATION), + arrayOf("int", ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM), + arrayOf("int", ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT), + arrayOf("double", ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION), + arrayOf("double", ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION), + arrayOf("int", ExifInterface.TAG_GAIN_CONTROL), + arrayOf("string", ExifInterface.TAG_ISO_SPEED_RATINGS), + arrayOf("string", ExifInterface.TAG_IMAGE_UNIQUE_ID), + arrayOf("int", ExifInterface.TAG_LIGHT_SOURCE), + arrayOf("string", ExifInterface.TAG_MAKER_NOTE), + arrayOf("double", ExifInterface.TAG_MAX_APERTURE_VALUE), + arrayOf("int", ExifInterface.TAG_METERING_MODE), + arrayOf("int", ExifInterface.TAG_NEW_SUBFILE_TYPE), + arrayOf("string", ExifInterface.TAG_OECF), + arrayOf("int", ExifInterface.TAG_PIXEL_X_DIMENSION), + arrayOf("int", ExifInterface.TAG_PIXEL_Y_DIMENSION), + arrayOf("string", ExifInterface.TAG_RELATED_SOUND_FILE), + arrayOf("int", ExifInterface.TAG_SATURATION), + arrayOf("int", ExifInterface.TAG_SCENE_CAPTURE_TYPE), + arrayOf("string", ExifInterface.TAG_SCENE_TYPE), + arrayOf("int", ExifInterface.TAG_SENSING_METHOD), + arrayOf("int", ExifInterface.TAG_SHARPNESS), + arrayOf("double", ExifInterface.TAG_SHUTTER_SPEED_VALUE), + arrayOf("string", ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE), + arrayOf("string", ExifInterface.TAG_SPECTRAL_SENSITIVITY), + arrayOf("int", ExifInterface.TAG_SUBFILE_TYPE), + arrayOf("string", ExifInterface.TAG_SUBSEC_TIME), + arrayOf("string", ExifInterface.TAG_SUBSEC_TIME_DIGITIZED), + arrayOf("string", ExifInterface.TAG_SUBSEC_TIME_ORIGINAL), + arrayOf("array", ExifInterface.TAG_SUBJECT_AREA), + arrayOf("double", ExifInterface.TAG_SUBJECT_DISTANCE), + arrayOf("int", ExifInterface.TAG_SUBJECT_DISTANCE_RANGE), + arrayOf("int", ExifInterface.TAG_SUBJECT_LOCATION), + arrayOf("string", ExifInterface.TAG_USER_COMMENT), + arrayOf("int", ExifInterface.TAG_WHITE_BALANCE), + arrayOf("int", ExifInterface.TAG_GPS_ALTITUDE_REF), + arrayOf("string", ExifInterface.TAG_GPS_AREA_INFORMATION), + arrayOf("double", ExifInterface.TAG_GPS_DOP), + arrayOf("string", ExifInterface.TAG_GPS_DATESTAMP), + arrayOf("double", ExifInterface.TAG_GPS_DEST_BEARING), + arrayOf("string", ExifInterface.TAG_GPS_DEST_BEARING_REF), + arrayOf("double", ExifInterface.TAG_GPS_DEST_DISTANCE), + arrayOf("string", ExifInterface.TAG_GPS_DEST_DISTANCE_REF), + arrayOf("double", ExifInterface.TAG_GPS_DEST_LATITUDE), + arrayOf("string", ExifInterface.TAG_GPS_DEST_LATITUDE_REF), + arrayOf("double", ExifInterface.TAG_GPS_DEST_LONGITUDE), + arrayOf("string", ExifInterface.TAG_GPS_DEST_LONGITUDE_REF), + arrayOf("int", ExifInterface.TAG_GPS_DIFFERENTIAL), + arrayOf("string", ExifInterface.TAG_GPS_H_POSITIONING_ERROR), + arrayOf("double", ExifInterface.TAG_GPS_IMG_DIRECTION), + arrayOf("string", ExifInterface.TAG_GPS_IMG_DIRECTION_REF), + arrayOf("string", ExifInterface.TAG_GPS_LATITUDE_REF), + arrayOf("string", ExifInterface.TAG_GPS_LONGITUDE_REF), + arrayOf("string", ExifInterface.TAG_GPS_MAP_DATUM), + arrayOf("string", ExifInterface.TAG_GPS_MEASURE_MODE), + arrayOf("string", ExifInterface.TAG_GPS_PROCESSING_METHOD), + arrayOf("string", ExifInterface.TAG_GPS_SATELLITES), + arrayOf("double", ExifInterface.TAG_GPS_SPEED), + arrayOf("string", ExifInterface.TAG_GPS_SPEED_REF), + arrayOf("string", ExifInterface.TAG_GPS_STATUS), + arrayOf("string", ExifInterface.TAG_GPS_TIMESTAMP), + arrayOf("double", ExifInterface.TAG_GPS_TRACK), + arrayOf("string", ExifInterface.TAG_GPS_TRACK_REF), + arrayOf("string", ExifInterface.TAG_GPS_VERSION_ID), + arrayOf("string", ExifInterface.TAG_INTEROPERABILITY_INDEX), + arrayOf("int", ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH), + arrayOf("int", ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH), + arrayOf("int", ExifInterface.TAG_DNG_VERSION), + arrayOf("int", ExifInterface.TAG_DEFAULT_CROP_SIZE), + arrayOf("int", ExifInterface.TAG_ORF_PREVIEW_IMAGE_START), + arrayOf("int", ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH), + arrayOf("int", ExifInterface.TAG_ORF_ASPECT_FRAME), + arrayOf("int", ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER), + arrayOf("int", ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER), + arrayOf("int", ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER), + arrayOf("int", ExifInterface.TAG_RW2_SENSOR_TOP_BORDER), + arrayOf("int", ExifInterface.TAG_RW2_ISO), + ) diff --git a/android/src/main/java/com/lodev09/exify/ExifyUtils.kt b/android/src/main/java/com/lodev09/exify/ExifyUtils.kt index 7ee0f12..3e331ee 100644 --- a/android/src/main/java/com/lodev09/exify/ExifyUtils.kt +++ b/android/src/main/java/com/lodev09/exify/ExifyUtils.kt @@ -13,9 +13,18 @@ object ExifyUtils { val attribute = exif.getAttribute(tag) if (attribute != null && attribute != "") { when (type) { - "string" -> tags.putString(tag, attribute) - "int" -> tags.putInt(tag, exif.getAttributeInt(tag, 0)) - "double" -> tags.putDouble(tag, exif.getAttributeDouble(tag, 0.0)) + "string" -> { + tags.putString(tag, attribute) + } + + "int" -> { + tags.putInt(tag, exif.getAttributeInt(tag, 0)) + } + + "double" -> { + tags.putDouble(tag, exif.getAttributeDouble(tag, 0.0)) + } + "array" -> { val array = Arguments.createArray() exif.getAttributeRange(tag)?.forEach { value -> diff --git a/eslint.config.mjs b/eslint.config.mjs index 16b00bb..e385a63 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -24,6 +24,15 @@ export default defineConfig([ }, }, { - ignores: ['node_modules/', 'lib/'], + ignores: [ + 'node_modules/', + 'lib/', + 'coverage/', + 'dist/', + 'ios/', + 'android/', + '.vscode/', + '.expo/', + ], }, ]); diff --git a/ios/Exify.h b/ios/Exify.h index bc9bb1d..29d95cd 100644 --- a/ios/Exify.h +++ b/ios/Exify.h @@ -1,6 +1,6 @@ #import -#import #import +#import #import @interface Exify : NSObject diff --git a/ios/Exify.mm b/ios/Exify.mm index 4543657..c101e7c 100644 --- a/ios/Exify.mm +++ b/ios/Exify.mm @@ -9,12 +9,11 @@ options.fetchLimit = 1; PHFetchResult *result = - [PHAsset fetchAssetsWithLocalIdentifiers:@[assetId] options:options]; + [PHAsset fetchAssetsWithLocalIdentifiers:@[ assetId ] options:options]; return result.firstObject; } -static void addTagEntries(CFStringRef dictionary, - NSDictionary *metadata, +static void addTagEntries(CFStringRef dictionary, NSDictionary *metadata, NSMutableDictionary *tags) { NSDictionary *entries = metadata[(__bridge NSString *)dictionary]; if (entries) { @@ -25,10 +24,12 @@ static void addTagEntries(CFStringRef dictionary, static NSDictionary *getExifTags(NSDictionary *metadata) { NSMutableDictionary *tags = [NSMutableDictionary new]; - NSString *compressionKey = (__bridge NSString *)kCGImageDestinationLossyCompressionQuality; + NSString *compressionKey = + (__bridge NSString *)kCGImageDestinationLossyCompressionQuality; for (NSString *key in metadata) { id value = metadata[key]; - if (![value isKindOfClass:[NSDictionary class]] && ![key isEqualToString:compressionKey]) { + if (![value isKindOfClass:[NSDictionary class]] && + ![key isEqualToString:compressionKey]) { tags[key] = value; } } @@ -42,7 +43,8 @@ static void addTagEntries(CFStringRef dictionary, } // Prefix GPS keys with "GPS" - NSDictionary *gps = metadata[(__bridge NSString *)kCGImagePropertyGPSDictionary]; + NSDictionary *gps = + metadata[(__bridge NSString *)kCGImagePropertyGPSDictionary]; if (gps) { for (NSString *key in gps) { tags[[@"GPS" stringByAppendingString:key]] = gps[key]; @@ -53,24 +55,31 @@ static void addTagEntries(CFStringRef dictionary, } static NSDictionary *readExifTags(NSURL *url) { - if (!url) return nil; + if (!url) + return nil; - CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url, nil); - if (!source) return nil; + CGImageSourceRef source = + CGImageSourceCreateWithURL((__bridge CFURLRef)url, nil); + if (!source) + return nil; NSDictionary *metadata = - (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, nil); + (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex( + source, 0, nil); CFRelease(source); - if (!metadata) return nil; + if (!metadata) + return nil; return getExifTags(metadata); } /// Returns @{@"metadata": NSDictionary, @"data": NSData} or nil on failure. static NSDictionary *updateMetadata(NSURL *url, NSDictionary *tags) { - CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)url, nil); - if (!imageSource) return nil; + CGImageSourceRef imageSource = + CGImageSourceCreateWithURL((__bridge CFURLRef)url, nil); + if (!imageSource) + return nil; CFStringRef sourceType = CGImageSourceGetType(imageSource); if (!sourceType) { @@ -78,14 +87,19 @@ static void addTagEntries(CFStringRef dictionary, return nil; } - CFDictionaryRef props = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil); - NSMutableDictionary *metadata = props - ? [NSMutableDictionary dictionaryWithDictionary:(__bridge_transfer NSDictionary *)props] - : [NSMutableDictionary new]; + CFDictionaryRef props = + CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil); + NSMutableDictionary *metadata = + props ? [NSMutableDictionary + dictionaryWithDictionary:(__bridge_transfer NSDictionary *) + props] + : [NSMutableDictionary new]; // Merge into Exif dict (filter out GPS-prefixed keys) - NSMutableDictionary *exifDict = [NSMutableDictionary dictionaryWithDictionary: - metadata[(__bridge NSString *)kCGImagePropertyExifDictionary] ?: @{}]; + NSMutableDictionary *exifDict = [NSMutableDictionary + dictionaryWithDictionary:metadata[(__bridge NSString *) + kCGImagePropertyExifDictionary] + ?: @{}]; for (NSString *key in tags) { if (![key hasPrefix:@"GPS"]) { exifDict[key] = tags[key]; @@ -94,28 +108,33 @@ static void addTagEntries(CFStringRef dictionary, metadata[(__bridge NSString *)kCGImagePropertyExifDictionary] = exifDict; // Handle GPS tags - NSMutableDictionary *gpsDict = [NSMutableDictionary dictionaryWithDictionary: - metadata[(__bridge NSString *)kCGImagePropertyGPSDictionary] ?: @{}]; + NSMutableDictionary *gpsDict = [NSMutableDictionary + dictionaryWithDictionary:metadata[(__bridge NSString *) + kCGImagePropertyGPSDictionary] + ?: @{}]; NSNumber *latitude = tags[@"GPSLatitude"]; if (latitude) { double lat = latitude.doubleValue; gpsDict[(__bridge NSString *)kCGImagePropertyGPSLatitude] = @(fabs(lat)); - gpsDict[(__bridge NSString *)kCGImagePropertyGPSLatitudeRef] = lat >= 0 ? @"N" : @"S"; + gpsDict[(__bridge NSString *)kCGImagePropertyGPSLatitudeRef] = + lat >= 0 ? @"N" : @"S"; } NSNumber *longitude = tags[@"GPSLongitude"]; if (longitude) { double lng = longitude.doubleValue; gpsDict[(__bridge NSString *)kCGImagePropertyGPSLongitude] = @(fabs(lng)); - gpsDict[(__bridge NSString *)kCGImagePropertyGPSLongitudeRef] = lng >= 0 ? @"E" : @"W"; + gpsDict[(__bridge NSString *)kCGImagePropertyGPSLongitudeRef] = + lng >= 0 ? @"E" : @"W"; } NSNumber *altitude = tags[@"GPSAltitude"]; if (altitude) { double alt = altitude.doubleValue; gpsDict[(__bridge NSString *)kCGImagePropertyGPSAltitude] = @(fabs(alt)); - gpsDict[(__bridge NSString *)kCGImagePropertyGPSAltitudeRef] = @(alt >= 0 ? 0 : 1); + gpsDict[(__bridge NSString *)kCGImagePropertyGPSAltitudeRef] = + @(alt >= 0 ? 0 : 1); } NSString *gpsDate = tags[@"GPSDateStamp"]; @@ -129,7 +148,8 @@ static void addTagEntries(CFStringRef dictionary, } metadata[(__bridge NSString *)kCGImagePropertyGPSDictionary] = gpsDict; - metadata[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(1); + metadata[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = + @(1); // Write image with updated metadata CGImageRef cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil); @@ -140,7 +160,7 @@ static void addTagEntries(CFStringRef dictionary, NSMutableData *destinationData = [NSMutableData new]; CGImageDestinationRef destination = CGImageDestinationCreateWithData( - (__bridge CFMutableDataRef)destinationData, sourceType, 1, nil); + (__bridge CFMutableDataRef)destinationData, sourceType, 1, nil); if (!destination) { CGImageRelease(cgImage); @@ -148,14 +168,15 @@ static void addTagEntries(CFStringRef dictionary, return nil; } - CGImageDestinationAddImage(destination, cgImage, (__bridge CFDictionaryRef)metadata); + CGImageDestinationAddImage(destination, cgImage, + (__bridge CFDictionaryRef)metadata); CGImageDestinationFinalize(destination); CFRelease(destination); CGImageRelease(cgImage); CFRelease(imageSource); - return @{@"metadata": metadata, @"data": destinationData}; + return @{@"metadata" : metadata, @"data" : destinationData}; } #pragma mark - Exify Module @@ -173,13 +194,17 @@ - (void)read:(NSString *)uri return; } - PHContentEditingInputRequestOptions *options = [PHContentEditingInputRequestOptions new]; + PHContentEditingInputRequestOptions *options = + [PHContentEditingInputRequestOptions new]; options.networkAccessAllowed = YES; [asset requestContentEditingInputWithOptions:options - completionHandler:^(PHContentEditingInput *contentInput, NSDictionary *info) { - resolve(readExifTags(contentInput.fullSizeImageURL)); - }]; + completionHandler:^( + PHContentEditingInput *contentInput, + NSDictionary *info) { + resolve(readExifTags( + contentInput.fullSizeImageURL)); + }]; } else { resolve(readExifTags([NSURL URLWithString:uri])); } @@ -197,42 +222,62 @@ - (void)write:(NSString *)uri return; } - PHContentEditingInputRequestOptions *options = [PHContentEditingInputRequestOptions new]; + PHContentEditingInputRequestOptions *options = + [PHContentEditingInputRequestOptions new]; options.networkAccessAllowed = YES; - [asset requestContentEditingInputWithOptions:options - completionHandler:^(PHContentEditingInput *contentInput, NSDictionary *info) { - if (!contentInput || !contentInput.fullSizeImageURL) { - reject(@"Error", @"Unable to read metadata from asset", nil); - return; - } - - NSDictionary *result = updateMetadata(contentInput.fullSizeImageURL, tags); - if (!result) { - reject(@"Error", @"Could not update metadata", nil); - return; - } - - __block NSString *newAssetId = nil; - NSError *error = nil; - [[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{ - PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAsset]; - [request addResourceWithType:PHAssetResourceTypePhoto data:result[@"data"] options:nil]; - request.creationDate = [NSDate date]; - newAssetId = request.placeholderForCreatedAsset.localIdentifier; - } error:&error]; - - if (error) { - reject(@"Error", @"Could not save to image file", error); - return; - } - - resolve(@{ - @"uri": [NSString stringWithFormat:@"ph://%@", newAssetId], - @"assetId": newAssetId ?: @"", - @"tags": getExifTags(result[@"metadata"]), - }); - }]; + [asset + requestContentEditingInputWithOptions:options + completionHandler:^( + PHContentEditingInput *contentInput, + NSDictionary *info) { + if (!contentInput || + !contentInput.fullSizeImageURL) { + reject(@"Error", + @"Unable to read metadata from asset", + nil); + return; + } + + NSDictionary *result = updateMetadata( + contentInput.fullSizeImageURL, tags); + if (!result) { + reject(@"Error", @"Could not update metadata", + nil); + return; + } + + __block NSString *newAssetId = nil; + NSError *error = nil; + [[PHPhotoLibrary sharedPhotoLibrary] + performChangesAndWait:^{ + PHAssetCreationRequest *request = + [PHAssetCreationRequest + creationRequestForAsset]; + [request addResourceWithType: + PHAssetResourceTypePhoto + data:result[@"data"] + options:nil]; + request.creationDate = [NSDate date]; + newAssetId = + request.placeholderForCreatedAsset + .localIdentifier; + } + error:&error]; + + if (error) { + reject(@"Error", + @"Could not save to image file", error); + return; + } + + resolve(@{ + @"uri" : [NSString + stringWithFormat:@"ph://%@", newAssetId], + @"assetId" : newAssetId ?: @"", + @"tags" : getExifTags(result[@"metadata"]), + }); + }]; } else { NSURL *url = [NSURL URLWithString:uri]; if (!url) { @@ -254,20 +299,18 @@ - (void)write:(NSString *)uri } resolve(@{ - @"uri": uri, - @"tags": getExifTags(result[@"metadata"]), + @"uri" : uri, + @"tags" : getExifTags(result[@"metadata"]), }); } } - (std::shared_ptr)getTurboModule: - (const facebook::react::ObjCTurboModule::InitParams &)params -{ - return std::make_shared(params); + (const facebook::react::ObjCTurboModule::InitParams &)params { + return std::make_shared(params); } -+ (NSString *)moduleName -{ ++ (NSString *)moduleName { return @"Exify"; } diff --git a/scripts/ktlint.sh b/scripts/ktlint.sh index 7a8a263..19b3018 100755 --- a/scripts/ktlint.sh +++ b/scripts/ktlint.sh @@ -1,7 +1,7 @@ #!/bin/bash if which ktlint >/dev/null; then - cd android && ktlint --color --relative --editorconfig=./.editorconfig -F ./**/*.kt* + cd android && ktlint --color --relative --editorconfig=../.editorconfig -F ./**/*.kt* else echo "error: KTLint not installed, install with 'brew install ktlint' (or manually from https://github.com/pinterest/ktlint)" exit 1 From 3284920cf31d69161615c4255487c5170816a15a Mon Sep 17 00:00:00 2001 From: lodev09 Date: Mon, 9 Feb 2026 05:28:11 +0800 Subject: [PATCH 2/4] fix(android): support HTTP URLs in read (#5) --- .../java/com/lodev09/exify/ExifyModule.kt | 53 ++++++++++++++----- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/android/src/main/java/com/lodev09/exify/ExifyModule.kt b/android/src/main/java/com/lodev09/exify/ExifyModule.kt index d528b81..802c27a 100644 --- a/android/src/main/java/com/lodev09/exify/ExifyModule.kt +++ b/android/src/main/java/com/lodev09/exify/ExifyModule.kt @@ -2,26 +2,36 @@ package com.lodev09.exify import android.net.Uri import androidx.exifinterface.media.ExifInterface -import com.lodev09.exify.ExifyUtils.formatTags import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableType +import com.lodev09.exify.ExifyUtils.formatTags import java.io.IOException private const val ERROR_TAG = "E_EXIFY_ERROR" -class ExifyModule(reactContext: ReactApplicationContext) : - NativeExifySpec(reactContext) { - +class ExifyModule( + reactContext: ReactApplicationContext, +) : NativeExifySpec(reactContext) { private val context = reactContext - override fun read(uri: String, promise: Promise) { + override fun read( + uri: String, + promise: Promise, + ) { val photoUri = Uri.parse(uri) try { - context.contentResolver.openInputStream(photoUri)?.use { + val inputStream = + if (photoUri.scheme == "http" || photoUri.scheme == "https") { + java.net.URL(uri).openStream() + } else { + context.contentResolver.openInputStream(photoUri) + } + + inputStream?.use { val tags = formatTags(ExifInterface(it)) promise.resolve(tags) } @@ -32,7 +42,11 @@ class ExifyModule(reactContext: ReactApplicationContext) : } @Throws(IOException::class) - override fun write(uri: String, tags: ReadableMap, promise: Promise) { + override fun write( + uri: String, + tags: ReadableMap, + promise: Promise, + ) { val photoUri = Uri.parse(uri) val params = Arguments.createMap() @@ -46,15 +60,28 @@ class ExifyModule(reactContext: ReactApplicationContext) : val type = tags.getType(tag) when (type) { - ReadableType.Boolean -> exif.setAttribute(tag, tags.getBoolean(tag).toString()) - ReadableType.Number -> + ReadableType.Boolean -> { + exif.setAttribute(tag, tags.getBoolean(tag).toString()) + } + + ReadableType.Number -> { when (valType) { "double" -> exif.setAttribute(tag, tags.getDouble(tag).toBigDecimal().toPlainString()) else -> exif.setAttribute(tag, tags.getDouble(tag).toInt().toString()) } - ReadableType.String -> exif.setAttribute(tag, tags.getString(tag)) - ReadableType.Array -> exif.setAttribute(tag, tags.getArray(tag).toString()) - else -> exif.setAttribute(tag, tags.getString(tag)) + } + + ReadableType.String -> { + exif.setAttribute(tag, tags.getString(tag)) + } + + ReadableType.Array -> { + exif.setAttribute(tag, tags.getArray(tag).toString()) + } + + else -> { + exif.setAttribute(tag, tags.getString(tag)) + } } } @@ -64,7 +91,7 @@ class ExifyModule(reactContext: ReactApplicationContext) : ) { exif.setLatLong( tags.getDouble(ExifInterface.TAG_GPS_LATITUDE), - tags.getDouble(ExifInterface.TAG_GPS_LONGITUDE) + tags.getDouble(ExifInterface.TAG_GPS_LONGITUDE), ) } From d47ad2e7670223a2565d5750486624d46c3aef11 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Mon, 9 Feb 2026 05:28:16 +0800 Subject: [PATCH 3/4] feat(example): add URL input for testing remote exif read --- example/package.json | 1 + example/src/App.tsx | 44 ++++++- example/src/components/PromptSheet.tsx | 163 +++++++++++++++++++++++++ yarn.lock | 24 ++++ 4 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 example/src/components/PromptSheet.tsx diff --git a/example/package.json b/example/package.json index b50965b..e0097dd 100644 --- a/example/package.json +++ b/example/package.json @@ -9,6 +9,7 @@ "ios": "expo run:ios" }, "dependencies": { + "@lodev09/react-native-true-sheet": "^3.8.2", "expo": "~54.0.33", "expo-camera": "~16.1.6", "expo-image": "~2.3.0", diff --git a/example/src/App.tsx b/example/src/App.tsx index cf95153..3547b8f 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,5 +1,12 @@ import { useEffect, useRef, useState } from 'react'; -import { StyleSheet, View, Pressable, Platform, Linking } from 'react-native'; +import { + StyleSheet, + View, + Pressable, + Platform, + Linking, + Text, +} from 'react-native'; import { CameraView, useCameraPermissions } from 'expo-camera'; import * as ImagePicker from 'expo-image-picker'; import * as MediaLibrary from 'expo-media-library'; @@ -8,9 +15,11 @@ import * as Exify from '@lodev09/react-native-exify'; import type { ExifTags } from '@lodev09/react-native-exify'; import { mockPosition, json } from './utils'; +import { PromptSheet, type PromptSheetRef } from './components/PromptSheet'; export default function App() { const cameraRef = useRef(null); + const promptRef = useRef(null); const [preview, setPreview] = useState(); const [cameraPermission, requestCameraPermission] = useCameraPermissions(); @@ -84,6 +93,22 @@ export default function App() { await readExif(uri); }; + const openUrl = async () => { + const defaultUrl = + 'https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg'; + + const url = await promptRef.current?.prompt( + 'Enter image URL', + defaultUrl, + 'https://' + ); + if (!url) return; + + console.log('openUrl:', url); + setPreview(url); + await readExif(url); + }; + if (!cameraPermission?.granted || !mediaPermission?.granted) { return ( @@ -105,6 +130,7 @@ export default function App() { return ( + {preview && ( @@ -114,7 +140,9 @@ export default function App() { - + + URL + ); @@ -171,7 +199,17 @@ const styles = StyleSheet.create({ borderRadius: 29, backgroundColor: '#fff', }, - spacer: { + urlButton: { width: 50, + height: 50, + borderRadius: 8, + backgroundColor: '#333', + alignItems: 'center', + justifyContent: 'center', + }, + urlLabel: { + color: '#fff', + fontSize: 13, + fontWeight: '600', }, }); diff --git a/example/src/components/PromptSheet.tsx b/example/src/components/PromptSheet.tsx new file mode 100644 index 0000000..f88d35c --- /dev/null +++ b/example/src/components/PromptSheet.tsx @@ -0,0 +1,163 @@ +import { + forwardRef, + useImperativeHandle, + useRef, + useState, + useCallback, + type ComponentRef, +} from 'react'; +import { StyleSheet, TextInput, Text, Pressable, View } from 'react-native'; +import { TrueSheet } from '@lodev09/react-native-true-sheet'; + +export interface PromptSheetRef { + prompt: ( + title: string, + defaultValue?: string, + placeholder?: string + ) => Promise; +} + +export const PromptSheet = forwardRef((_props, ref) => { + const sheetRef = useRef(null); + const inputRef = useRef>(null); + const resolveRef = useRef<((value: string | null) => void) | null>(null); + + const [title, setTitle] = useState(''); + const [placeholder, setPlaceholder] = useState(''); + const [value, setValue] = useState(''); + + useImperativeHandle(ref, () => ({ + prompt: (promptTitle, defaultValue = '', promptPlaceholder = '') => { + setTitle(promptTitle); + setValue(defaultValue); + setPlaceholder(promptPlaceholder); + return new Promise((resolve) => { + resolveRef.current = resolve; + sheetRef.current?.present(); + }); + }, + })); + + const handlePresent = useCallback(() => { + inputRef.current?.focus(); + }, []); + + const handleSubmit = useCallback(() => { + resolveRef.current?.(value); + resolveRef.current = null; + sheetRef.current?.dismiss(); + }, [value]); + + const handleDismiss = useCallback(() => { + resolveRef.current?.(null); + resolveRef.current = null; + }, []); + + return ( + + + {title} + + + {value.length > 0 && ( + setValue('')}> + + + )} + + + sheetRef.current?.dismiss()} + > + Cancel + + + OK + + + + + ); +}); + +const styles = StyleSheet.create({ + content: { + padding: 20, + paddingBottom: 32, + }, + title: { + color: '#fff', + fontSize: 17, + fontWeight: '600', + marginBottom: 16, + }, + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#2c2c2e', + borderRadius: 10, + marginBottom: 20, + }, + input: { + flex: 1, + color: '#fff', + fontSize: 15, + padding: 12, + }, + clearButton: { + padding: 10, + }, + clearText: { + color: '#666', + fontSize: 14, + }, + buttons: { + flexDirection: 'row', + gap: 10, + }, + button: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 12, + borderRadius: 10, + backgroundColor: '#2c2c2e', + }, + cancelText: { + color: '#999', + fontSize: 15, + fontWeight: '600', + }, + submitButton: { + backgroundColor: '#0a84ff', + }, + submitText: { + color: '#fff', + fontSize: 15, + fontWeight: '600', + }, +}); diff --git a/yarn.lock b/yarn.lock index 5f488ad..5f51022 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2959,6 +2959,29 @@ __metadata: languageName: unknown linkType: soft +"@lodev09/react-native-true-sheet@npm:^3.8.2": + version: 3.8.2 + resolution: "@lodev09/react-native-true-sheet@npm:3.8.2" + peerDependencies: + "@gorhom/bottom-sheet": ">=5" + "@react-navigation/native": ">=7" + react: "*" + react-native: "*" + react-native-reanimated: ">=4" + react-native-worklets: "*" + peerDependenciesMeta: + "@gorhom/bottom-sheet": + optional: true + "@react-navigation/native": + optional: true + react-native-reanimated: + optional: true + react-native-worklets: + optional: true + checksum: 10c0/28e220e1ddb11f01027f8266ab79eb4982f8b504cbeb942aded99044d5ffd4f160022bf27d822bf89d8a766eefa4d7d46e2bda85d9bc3a3fe099f63f4c0dca07 + languageName: node + linkType: hard + "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": version: 5.1.1-v1 resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" @@ -10927,6 +10950,7 @@ __metadata: version: 0.0.0-use.local resolution: "react-native-exify-example@workspace:example" dependencies: + "@lodev09/react-native-true-sheet": "npm:^3.8.2" expo: "npm:~54.0.33" expo-camera: "npm:~16.1.6" expo-dev-client: "npm:~6.0.20" From ab8c05d6392700b1b49b977946306b20c4118d53 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Mon, 9 Feb 2026 05:30:50 +0800 Subject: [PATCH 4/4] fix: reject with errors instead of resolving null --- .../java/com/lodev09/exify/ExifyModule.kt | 22 +++++++-- example/src/App.tsx | 2 +- ios/Exify.h | 1 + ios/Exify.mm | 46 +++++++++++++++++-- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/lodev09/exify/ExifyModule.kt b/android/src/main/java/com/lodev09/exify/ExifyModule.kt index 802c27a..98fd022 100644 --- a/android/src/main/java/com/lodev09/exify/ExifyModule.kt +++ b/android/src/main/java/com/lodev09/exify/ExifyModule.kt @@ -7,6 +7,7 @@ import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableType +import com.facebook.react.util.RNLog import com.lodev09.exify.ExifyUtils.formatTags import java.io.IOException @@ -22,22 +23,35 @@ class ExifyModule( promise: Promise, ) { val photoUri = Uri.parse(uri) + val scheme = photoUri.scheme + + if (scheme == null) { + RNLog.w(context, "Exify: Invalid URI: $uri") + promise.reject(ERROR_TAG, "Invalid URI: $uri") + return + } try { val inputStream = - if (photoUri.scheme == "http" || photoUri.scheme == "https") { + if (scheme == "http" || scheme == "https") { java.net.URL(uri).openStream() } else { context.contentResolver.openInputStream(photoUri) } - inputStream?.use { + if (inputStream == null) { + RNLog.w(context, "Exify: Could not open URI: $uri") + promise.reject(ERROR_TAG, "Could not open URI: $uri") + return + } + + inputStream.use { val tags = formatTags(ExifInterface(it)) promise.resolve(tags) } } catch (e: Exception) { - promise.resolve(null) - e.printStackTrace() + RNLog.w(context, "Exify: ${e.message}") + promise.reject(ERROR_TAG, e.message, e) } } diff --git a/example/src/App.tsx b/example/src/App.tsx index 3547b8f..7a9711a 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -105,7 +105,7 @@ export default function App() { if (!url) return; console.log('openUrl:', url); - setPreview(url); + // setPreview(url); await readExif(url); }; diff --git a/ios/Exify.h b/ios/Exify.h index 29d95cd..232465e 100644 --- a/ios/Exify.h +++ b/ios/Exify.h @@ -1,6 +1,7 @@ #import #import #import +#import #import @interface Exify : NSObject diff --git a/ios/Exify.mm b/ios/Exify.mm index c101e7c..6b0f011 100644 --- a/ios/Exify.mm +++ b/ios/Exify.mm @@ -190,7 +190,8 @@ - (void)read:(NSString *)uri NSString *assetId = [uri substringFromIndex:5]; PHAsset *asset = getAssetById(assetId); if (!asset) { - resolve(nil); + RCTLogWarn(@"Exify: Could not retrieve asset"); + reject(@"Error", @"Could not retrieve asset", nil); return; } @@ -202,11 +203,48 @@ - (void)read:(NSString *)uri completionHandler:^( PHContentEditingInput *contentInput, NSDictionary *info) { - resolve(readExifTags( - contentInput.fullSizeImageURL)); + if (!contentInput || + !contentInput.fullSizeImageURL) { + RCTLogWarn(@"Exify: Could not read asset " + "content"); + reject(@"Error", + @"Could not read asset content", nil); + return; + } + + NSDictionary *tags = readExifTags( + contentInput.fullSizeImageURL); + if (!tags) { + RCTLogWarn(@"Exify: Could not read metadata " + "from asset"); + reject(@"Error", + @"Could not read metadata from asset", + nil); + return; + } + + resolve(tags); }]; } else { - resolve(readExifTags([NSURL URLWithString:uri])); + NSURL *url = [NSURL URLWithString:uri]; + if (!url) { + RCTLogWarn(@"Exify: Invalid URI: %@", uri); + reject(@"Error", [NSString stringWithFormat:@"Invalid URI: %@", uri], + nil); + return; + } + + NSDictionary *tags = readExifTags(url); + if (!tags) { + RCTLogWarn(@"Exify: Could not read metadata from: %@", uri); + reject( + @"Error", + [NSString stringWithFormat:@"Could not read metadata from: %@", uri], + nil); + return; + } + + resolve(tags); } }