react-native-attachment-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +327 -0
- package/android/CMakeLists.txt +31 -0
- package/android/build.gradle +121 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/libs/rnattachmentkit-release.aar +0 -0
- package/android/src/newarch/java/com/rnattachmentkit/RNAttachmentKitModule.kt +1159 -0
- package/android/src/newarch/java/com/rnattachmentkit/RNAttachmentKitPackage.kt +37 -0
- package/android/src/oldarch/java/com/rnattachmentkit/NativeRNAttachmentKitSpec.kt +19 -0
- package/android/src/oldarch/java/com/rnattachmentkit/RNAttachmentKitModule.kt +1142 -0
- package/android/src/oldarch/java/com/rnattachmentkit/RNAttachmentKitPackage.kt +19 -0
- package/ios/README.md +213 -0
- package/ios/RNAttachmentKit.m +132 -0
- package/ios/RNAttachmentKit.swift +1787 -0
- package/ios/RNAttachmentKit.xcframework/Info.plist +44 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Assets.car +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Base.lproj/LaunchScreen.storyboardc/Info.plist +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Headers/RNAttachmentKit-Swift.h +581 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Info.plist +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.abi.json +34376 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.private.swiftinterface +517 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios.swiftinterface +517 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Modules/module.modulemap +3 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/Pretendard-SemiBold.otf +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/RNAttachmentKit +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeRequirements +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeResources +498 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64/RNAttachmentKit.framework/_CodeSignature/CodeSignature +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Assets.car +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Base.lproj/LaunchScreen.storyboardc/Info.plist +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Headers/RNAttachmentKit-Swift.h +1158 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Info.plist +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.abi.json +34376 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +517 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/arm64-apple-ios-simulator.swiftinterface +517 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.abi.json +34376 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +517 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/RNAttachmentKit.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +517 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Modules/module.modulemap +3 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/Pretendard-SemiBold.otf +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/RNAttachmentKit +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_reflect~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_crop_rotate~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_delete~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_forward~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_drawing_reward~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_crop~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_drawing~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_effect~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_emoji~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_eraser~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_mosaic~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~dark.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/ToolbarIcons/ic_editor_text~light.webp +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/_CodeSignature/CodeRequirements +0 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/_CodeSignature/CodeResources +553 -0
- package/ios/RNAttachmentKit.xcframework/ios-arm64_x86_64-simulator/RNAttachmentKit.framework/_CodeSignature/CodeSignature +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/Contents.json +6 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_back.imageset/Contents.json +24 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_back.imageset/ic_back_dark.png +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_back.imageset/ic_back_normal.png +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_drawing_delete.imageset/Contents.json +56 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_drawing_delete.imageset/delete_dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_drawing_delete.imageset/delete_normal.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_drawing_forward.imageset/Contents.json +56 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_drawing_forward.imageset/forward_dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_drawing_forward.imageset/forward_normal.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_drawing_reward.imageset/Contents.json +56 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_drawing_reward.imageset/reward_dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_drawing_reward.imageset/reward_normal.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_crop.imageset/Contents.json +52 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_crop.imageset/crop_dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_crop.imageset/crop_normal.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_drawing.imageset/Contents.json +52 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_drawing.imageset/drawing_dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_drawing.imageset/drawing_normal.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_effect.imageset/Contents.json +52 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_effect.imageset/effect_dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_effect.imageset/effect_normal.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_emoji.imageset/Contents.json +52 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_emoji.imageset/emoji_dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_emoji.imageset/emoji_normal.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_text.imageset/Contents.json +52 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_text.imageset/text_dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/ic_editor_text.imageset/text_norml.webp +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/rotate_icon.imageset/Contents.json +32 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/rotate_icon.imageset/rotate_dark.png +0 -0
- package/ios/RNAttachmentKitAssets/Assets.xcassets/rotate_icon.imageset/rotate_normal.png +0 -0
- package/ios/RNAttachmentKitAssets/Fonts/Pretendard-SemiBold.otf +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_reflect~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_reflect~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_rotate~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_crop_rotate~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_delete~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_delete~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_forward~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_forward~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_reward~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_drawing_reward~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_crop~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_crop~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_drawing~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_drawing~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_effect~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_effect~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_emoji~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_emoji~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_eraser~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_eraser~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_mosaic~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_mosaic~light.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_text~dark.webp +0 -0
- package/ios/RNAttachmentKitAssets/ToolbarIcons/ic_editor_text~light.webp +0 -0
- package/package.json +68 -0
- package/react-native-attachment-kit.podspec +51 -0
- package/react-native.config.js +14 -0
- package/src/NativeAttachmentKit.ts +26 -0
- package/src/index.d.ts +56 -0
- package/src/index.ts +359 -0
- package/src/types.ts +317 -0
|
@@ -0,0 +1,1159 @@
|
|
|
1
|
+
package com.rnattachmentkit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import android.app.Activity
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.graphics.Bitmap
|
|
7
|
+
import android.graphics.BitmapFactory
|
|
8
|
+
import android.net.Uri
|
|
9
|
+
import com.facebook.react.bridge.*
|
|
10
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
11
|
+
import android.Manifest
|
|
12
|
+
import android.content.pm.PackageManager
|
|
13
|
+
import android.os.Build
|
|
14
|
+
import androidx.core.content.ContextCompat
|
|
15
|
+
import androidx.fragment.app.FragmentActivity
|
|
16
|
+
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
17
|
+
import com.facebook.react.modules.core.PermissionListener
|
|
18
|
+
import kotlinx.coroutines.CoroutineScope
|
|
19
|
+
import kotlinx.coroutines.Dispatchers
|
|
20
|
+
import kotlinx.coroutines.launch
|
|
21
|
+
import kotlinx.coroutines.withContext
|
|
22
|
+
import java.io.*
|
|
23
|
+
|
|
24
|
+
@ReactModule(name = RNAttachmentKitModule.NAME)
|
|
25
|
+
class RNAttachmentKitModule(reactContext: ReactApplicationContext) :
|
|
26
|
+
NativeAttachmentKitSpec(reactContext), ActivityEventListener {
|
|
27
|
+
|
|
28
|
+
companion object {
|
|
29
|
+
const val NAME = "RNAttachmentKit"
|
|
30
|
+
private const val E_PICKER_CANCELLED = "E_PICKER_CANCELLED"
|
|
31
|
+
private const val E_NO_ACTIVITY = "E_NO_ACTIVITY"
|
|
32
|
+
private const val E_FAILED_TO_PICK = "E_FAILED_TO_PICK"
|
|
33
|
+
private const val REQUEST_CODE_IMAGE_PICKER = 3001
|
|
34
|
+
private const val REQUEST_CODE_IMAGE_EDITOR = 3002
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private var pickImagesPromise: Promise? = null
|
|
38
|
+
private var editImagePromise: Promise? = null
|
|
39
|
+
private val moduleScope = CoroutineScope(Dispatchers.Main)
|
|
40
|
+
private var maxWidth: Int = 1024
|
|
41
|
+
private var maxHeight: Int = 1024
|
|
42
|
+
private var outputFormat: String = "webp" // Default to webp
|
|
43
|
+
|
|
44
|
+
// newarch에는 이 맵과 getDefaultAnimationConfig() 구현이 통째로 빠져 있었습니다(oldarch에는 있었음).
|
|
45
|
+
// NativeAttachmentKitSpec(codegen 생성 추상 클래스)이 이 메서드를 abstract로 요구하기 때문에,
|
|
46
|
+
// 누락된 상태로는 아예 컴파일이 안 됩니다(newarch는 AAR이 아니라 소스 형태로 배포되어 소비 앱이
|
|
47
|
+
// 직접 컴파일하므로, 이 파일을 실제로 빌드해보기 전까지는 드러나지 않았던 것으로 보입니다).
|
|
48
|
+
private val defaultAnimationConfig = mapOf(
|
|
49
|
+
"galleryOpen" to 300,
|
|
50
|
+
"galleryClose" to 250,
|
|
51
|
+
"editorOpen" to 250,
|
|
52
|
+
"editorClose" to 200,
|
|
53
|
+
"viewerOpen" to 250,
|
|
54
|
+
"viewerClose" to 200
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
// newarch에는 이 리시버가 누락되어 있었습니다(oldarch에는 있었음). 그 결과 네이티브가
|
|
58
|
+
// VIEWER_WILL_CLOSE 로컬 브로드캐스트를 보내도 JS의 "onViewerWillClose" 이벤트가 한 번도
|
|
59
|
+
// emit되지 않아서, JS(index.ts)의 리스너 정리 로직(subscriptionPageSelected.remove() 등)이
|
|
60
|
+
// 실행되지 않고 계속 leak됐습니다. 그 결과 이전에 열었던 이미지의 onPageSelected 구독이 살아있는
|
|
61
|
+
// 채로 다음 이미지를 열면, 두 구독이 동시에 반응해서 "이전 이미지의 위치 정보"로
|
|
62
|
+
// updateSourceRect()가 잘못 호출되어 닫기 애니메이션이 엉뚱한(이전) 위치로 돌아가는
|
|
63
|
+
// 버그의 원인이었습니다.
|
|
64
|
+
private val viewerWillCloseReceiver = object : android.content.BroadcastReceiver() {
|
|
65
|
+
override fun onReceive(context: android.content.Context?, intent: android.content.Intent?) {
|
|
66
|
+
if (intent?.action == "com.rnattachmentkit.VIEWER_WILL_CLOSE") {
|
|
67
|
+
reactApplicationContext
|
|
68
|
+
.getJSModule(com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
69
|
+
.emit("onViewerWillClose", Arguments.createMap())
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private val pageChangeReceiver = object : android.content.BroadcastReceiver() {
|
|
75
|
+
override fun onReceive(context: android.content.Context?, intent: android.content.Intent?) {
|
|
76
|
+
if (intent?.action == "com.rnattachmentkit.PAGE_CHANGED") {
|
|
77
|
+
val index = intent.getIntExtra("index", 0)
|
|
78
|
+
val params = Arguments.createMap()
|
|
79
|
+
params.putInt("index", index)
|
|
80
|
+
reactApplicationContext
|
|
81
|
+
.getJSModule(com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
82
|
+
.emit("onPageSelected", params)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 이미지 뷰어 상단바의 삭제를 사용자가 확정했을 때(ImageViewerOverlayView.handleDeleteTapped).
|
|
88
|
+
// iOS의 "onImageDeleted" 이벤트와 동일한 body(index, url)로 JS에 알립니다 - 실제 메시지 삭제는 RN이 담당.
|
|
89
|
+
private val imageDeletedReceiver = object : android.content.BroadcastReceiver() {
|
|
90
|
+
override fun onReceive(context: android.content.Context?, intent: android.content.Intent?) {
|
|
91
|
+
if (intent?.action == "com.rnattachmentkit.IMAGE_DELETED") {
|
|
92
|
+
val params = Arguments.createMap()
|
|
93
|
+
params.putInt("index", intent.getIntExtra("index", 0))
|
|
94
|
+
params.putString("url", intent.getStringExtra("url") ?: "")
|
|
95
|
+
// patch405: 사용자가 고른 삭제 방식(deleteOptions 의 key). 선택지가 없었으면 생략.
|
|
96
|
+
intent.getStringExtra("option")?.let { params.putString("option", it) }
|
|
97
|
+
reactApplicationContext
|
|
98
|
+
.getJSModule(com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
99
|
+
.emit("onImageDeleted", params)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 열기 애니메이션이 끝난 시점을 JS에 알리는 이벤트 (배경 스크롤 보정 타이밍용).
|
|
105
|
+
private val viewerOpenedReceiver = object : android.content.BroadcastReceiver() {
|
|
106
|
+
override fun onReceive(context: android.content.Context?, intent: android.content.Intent?) {
|
|
107
|
+
if (intent?.action == "com.rnattachmentkit.VIEWER_OPENED") {
|
|
108
|
+
reactApplicationContext
|
|
109
|
+
.getJSModule(com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
110
|
+
.emit("onViewerOpened", Arguments.createMap())
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
init {
|
|
116
|
+
reactContext.addActivityEventListener(this)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
override fun getDefaultAnimationConfig(promise: Promise) {
|
|
120
|
+
val config = Arguments.createMap().apply {
|
|
121
|
+
putInt("galleryOpen", defaultAnimationConfig["galleryOpen"] ?: 300)
|
|
122
|
+
putInt("galleryClose", defaultAnimationConfig["galleryClose"] ?: 250)
|
|
123
|
+
putInt("editorOpen", defaultAnimationConfig["editorOpen"] ?: 250)
|
|
124
|
+
putInt("editorClose", defaultAnimationConfig["editorClose"] ?: 200)
|
|
125
|
+
putInt("viewerOpen", defaultAnimationConfig["viewerOpen"] ?: 250)
|
|
126
|
+
putInt("viewerClose", defaultAnimationConfig["viewerClose"] ?: 200)
|
|
127
|
+
}
|
|
128
|
+
promise.resolve(config)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
override fun initialize() {
|
|
132
|
+
super.initialize()
|
|
133
|
+
androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(reactApplicationContext)
|
|
134
|
+
.registerReceiver(pageChangeReceiver, android.content.IntentFilter("com.rnattachmentkit.PAGE_CHANGED"))
|
|
135
|
+
androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(reactApplicationContext)
|
|
136
|
+
.registerReceiver(viewerOpenedReceiver, android.content.IntentFilter("com.rnattachmentkit.VIEWER_OPENED"))
|
|
137
|
+
androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(reactApplicationContext)
|
|
138
|
+
.registerReceiver(viewerWillCloseReceiver, android.content.IntentFilter("com.rnattachmentkit.VIEWER_WILL_CLOSE"))
|
|
139
|
+
androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(reactApplicationContext)
|
|
140
|
+
.registerReceiver(imageDeletedReceiver, android.content.IntentFilter("com.rnattachmentkit.IMAGE_DELETED"))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
override fun updateSourceRect(options: ReadableMap, promise: Promise) {
|
|
144
|
+
var startX = -1f
|
|
145
|
+
var startY = -1f
|
|
146
|
+
var startWidth = -1f
|
|
147
|
+
var startHeight = -1f
|
|
148
|
+
|
|
149
|
+
if (options.hasKey("x")) {
|
|
150
|
+
startX = com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(options.getDouble("x").toFloat())
|
|
151
|
+
}
|
|
152
|
+
if (options.hasKey("y")) {
|
|
153
|
+
startY = com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(options.getDouble("y").toFloat())
|
|
154
|
+
}
|
|
155
|
+
if (options.hasKey("width")) {
|
|
156
|
+
startWidth = com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(options.getDouble("width").toFloat())
|
|
157
|
+
}
|
|
158
|
+
if (options.hasKey("height")) {
|
|
159
|
+
startHeight = com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(options.getDouble("height").toFloat())
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 브로드캐스트가 유실되는 타이밍(Fragment의 onViewCreated 전) 대비, sticky 값도 함께 저장.
|
|
163
|
+
// (useDialogViewer/useOverlayViewer 중 실제 사용 중인 쪽만 값을 읽으므로 둘 다 갱신해도 무해함)
|
|
164
|
+
ImageViewerDialogFragment.updatePendingCoordinates(startX, startY, startWidth, startHeight)
|
|
165
|
+
ImageViewerOverlayView.updatePendingCoordinates(startX, startY, startWidth, startHeight)
|
|
166
|
+
|
|
167
|
+
val intent = android.content.Intent("com.rnattachmentkit.UPDATE_COORDINATES")
|
|
168
|
+
intent.putExtra("startX", startX)
|
|
169
|
+
intent.putExtra("startY", startY)
|
|
170
|
+
intent.putExtra("startWidth", startWidth)
|
|
171
|
+
intent.putExtra("startHeight", startHeight)
|
|
172
|
+
// 그동안 sourceBorderCorners(어느 모서리를 둥글릴지)가 여기서 전달되지 않아서, 그룹 이미지에서
|
|
173
|
+
// 스와이프로 다른 셀(예: 오른쪽 위 모서리 이미지)로 이동해도 마스크의 라운드 코너 패턴이
|
|
174
|
+
// 처음 열었던 이미지의 것으로 고정된 채 안 바뀌는 문제가 있었습니다. JS가 onPageSelected에서
|
|
175
|
+
// 매번 새로 계산해서 보내주는 sourceBorderCorners를 브로드캐스트에 실어 보내도록 수정합니다.
|
|
176
|
+
if (options.hasKey("sourceBorderCorners")) {
|
|
177
|
+
val cornersArray = options.getArray("sourceBorderCorners")
|
|
178
|
+
if (cornersArray != null) {
|
|
179
|
+
val corners = ArrayList<String>()
|
|
180
|
+
for (i in 0 until cornersArray.size()) {
|
|
181
|
+
cornersArray.getString(i)?.let { corners.add(it) }
|
|
182
|
+
}
|
|
183
|
+
intent.putStringArrayListExtra("sourceBorderCorners", corners)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(reactApplicationContext)
|
|
188
|
+
.sendBroadcast(intent)
|
|
189
|
+
|
|
190
|
+
promise.resolve(null)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
override fun getName(): String = NAME
|
|
194
|
+
|
|
195
|
+
override fun init(licenseKey: String, promise: Promise) {
|
|
196
|
+
val result = LicenseManager.initialize(reactApplicationContext, licenseKey)
|
|
197
|
+
promise.resolve(result)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
override fun openEditor(options: ReadableMap, promise: Promise) {
|
|
201
|
+
val activity = getCurrentActivity()
|
|
202
|
+
if (activity == null) {
|
|
203
|
+
promise.reject(E_NO_ACTIVITY, "Activity doesn't exist")
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
val uriStr = if (options.hasKey("uri")) options.getString("uri") else null
|
|
208
|
+
if (uriStr == null) {
|
|
209
|
+
promise.reject(E_FAILED_TO_PICK, "uri is required")
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
val themeColor = if (options.hasKey("themeColor")) options.getString("themeColor") else null
|
|
214
|
+
|
|
215
|
+
if (options.hasKey("maxWidth")) maxWidth = options.getInt("maxWidth")
|
|
216
|
+
if (options.hasKey("maxHeight")) maxHeight = options.getInt("maxHeight")
|
|
217
|
+
|
|
218
|
+
editImagePromise = promise
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
val intent = ImageEditorActivity.createIntent(
|
|
222
|
+
activity = activity,
|
|
223
|
+
uris = listOf(Uri.parse(uriStr)),
|
|
224
|
+
startIndex = 0,
|
|
225
|
+
themeColor = themeColor,
|
|
226
|
+
singlePhotoMode = true
|
|
227
|
+
)
|
|
228
|
+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
229
|
+
activity.startActivityForResult(intent, REQUEST_CODE_IMAGE_EDITOR)
|
|
230
|
+
}, 50)
|
|
231
|
+
} catch (e: Exception) {
|
|
232
|
+
promise.reject(E_FAILED_TO_PICK, "Failed to open image editor: ${e.message}", e)
|
|
233
|
+
editImagePromise = null
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@ReactMethod
|
|
238
|
+
override fun openGallery(options: ReadableMap, promise: Promise) {
|
|
239
|
+
val activity = getCurrentActivity()
|
|
240
|
+
|
|
241
|
+
if (activity == null) {
|
|
242
|
+
promise.reject(E_NO_ACTIVITY, "Activity doesn't exist")
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Parse options
|
|
247
|
+
val autoCloseOnSelect = if (options.hasKey("autoCloseOnSelect")) {
|
|
248
|
+
options.getBoolean("autoCloseOnSelect")
|
|
249
|
+
} else false
|
|
250
|
+
|
|
251
|
+
// 선택 방식 (selectionMode: 'single' | 'multi', 생략 시 기존 규칙)
|
|
252
|
+
// - 'multi' : maxSelection 이 1 이어도 멀티와 똑같이 (autoCloseOnSelect 무시, 1 미만이면 1장)
|
|
253
|
+
// - 'single': 기존 단일 동작 (maxSelection 을 1로, autoCloseOnSelect 면 0)
|
|
254
|
+
// - 생략 : 기존 그대로 (autoCloseOnSelect → 0, 아니면 maxSelection, 기본 1)
|
|
255
|
+
val selectionMode = if (options.hasKey("selectionMode")) options.getString("selectionMode")?.lowercase() else null
|
|
256
|
+
val forceMultiMode = selectionMode == "multi"
|
|
257
|
+
val maxSelection = if (forceMultiMode) {
|
|
258
|
+
(if (options.hasKey("maxSelection")) options.getInt("maxSelection") else 1).coerceAtLeast(1)
|
|
259
|
+
} else if (autoCloseOnSelect) {
|
|
260
|
+
0
|
|
261
|
+
} else if (selectionMode == "single") {
|
|
262
|
+
1
|
|
263
|
+
} else if (options.hasKey("maxSelection")) {
|
|
264
|
+
options.getInt("maxSelection")
|
|
265
|
+
} else {
|
|
266
|
+
1
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
val languageCode = if (options.hasKey("languageCode")) {
|
|
270
|
+
options.getString("languageCode") ?: "en"
|
|
271
|
+
} else {
|
|
272
|
+
"en"
|
|
273
|
+
}
|
|
274
|
+
// 편집기/크롭 등 다른 화면들은 TurboImagePickerConfig.languageCode(전역)로 로케일을 잡는데,
|
|
275
|
+
// 그동안 JS가 넘긴 languageCode를 여기에 반영하는 코드가 없어서 항상 기본값 "en"이었습니다.
|
|
276
|
+
TurboImagePickerConfig.languageCode = languageCode
|
|
277
|
+
|
|
278
|
+
val themeColor = if (options.hasKey("themeColor")) {
|
|
279
|
+
options.getString("themeColor")
|
|
280
|
+
} else {
|
|
281
|
+
null
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
maxWidth = if (options.hasKey("maxWidth")) {
|
|
285
|
+
val value = options.getInt("maxWidth")
|
|
286
|
+
if (value > 0) value else 1024
|
|
287
|
+
} else {
|
|
288
|
+
1024
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
maxHeight = if (options.hasKey("maxHeight")) {
|
|
292
|
+
val value = options.getInt("maxHeight")
|
|
293
|
+
if (value > 0) value else 1024
|
|
294
|
+
} else {
|
|
295
|
+
1024
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
outputFormat = if (options.hasKey("outputFormat")) {
|
|
299
|
+
options.getString("outputFormat")?.lowercase() ?: "webp"
|
|
300
|
+
} else if (options.hasKey("format")) {
|
|
301
|
+
options.getString("format")?.lowercase() ?: "webp"
|
|
302
|
+
} else {
|
|
303
|
+
"webp"
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
val enableEditor = if (options.hasKey("enableEditor")) {
|
|
307
|
+
options.getBoolean("enableEditor")
|
|
308
|
+
} else {
|
|
309
|
+
false
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
val profileMode = if (options.hasKey("profileMode")) {
|
|
313
|
+
options.getBoolean("profileMode")
|
|
314
|
+
} else {
|
|
315
|
+
false
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
val fragmentActivity = activity as? FragmentActivity
|
|
319
|
+
if (fragmentActivity == null) {
|
|
320
|
+
promise.reject(E_NO_ACTIVITY, "Activity is not a FragmentActivity")
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
pickImagesPromise = promise
|
|
325
|
+
|
|
326
|
+
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
327
|
+
Manifest.permission.READ_MEDIA_IMAGES
|
|
328
|
+
} else {
|
|
329
|
+
Manifest.permission.READ_EXTERNAL_STORAGE
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
val showSheet = {
|
|
333
|
+
fragmentActivity.runOnUiThread {
|
|
334
|
+
try {
|
|
335
|
+
val bottomSheet = ImagePickerBottomSheet.newInstance(
|
|
336
|
+
maxSelection, languageCode, enableEditor, profileMode, maxWidth, maxHeight, themeColor,
|
|
337
|
+
forceMultiMode = forceMultiMode
|
|
338
|
+
)
|
|
339
|
+
bottomSheet.setOnImagesSelectedListener { uris ->
|
|
340
|
+
val result = WritableNativeArray()
|
|
341
|
+
for (uri in uris) {
|
|
342
|
+
result.pushString(uri.toString())
|
|
343
|
+
}
|
|
344
|
+
handleSelectedImages(result, pickImagesPromise)
|
|
345
|
+
pickImagesPromise = null
|
|
346
|
+
}
|
|
347
|
+
bottomSheet.setOnCancelledListener {
|
|
348
|
+
pickImagesPromise?.reject(E_PICKER_CANCELLED, "User cancelled image picker")
|
|
349
|
+
pickImagesPromise = null
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
val existingFragment = fragmentActivity.supportFragmentManager.findFragmentByTag("ImagePickerBottomSheet")
|
|
353
|
+
existingFragment?.let {
|
|
354
|
+
fragmentActivity.supportFragmentManager.beginTransaction().remove(it).commitAllowingStateLoss()
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fragmentActivity.supportFragmentManager.beginTransaction()
|
|
358
|
+
.add(bottomSheet, "ImagePickerBottomSheet")
|
|
359
|
+
.commitAllowingStateLoss()
|
|
360
|
+
|
|
361
|
+
} catch (e: Exception) {
|
|
362
|
+
pickImagesPromise?.reject(E_FAILED_TO_PICK, "Failed to open image picker: ${e.message}", e)
|
|
363
|
+
pickImagesPromise = null
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (ContextCompat.checkSelfPermission(fragmentActivity, permission) == PackageManager.PERMISSION_GRANTED) {
|
|
369
|
+
showSheet()
|
|
370
|
+
} else {
|
|
371
|
+
val permissionAwareActivity = fragmentActivity as? PermissionAwareActivity
|
|
372
|
+
if (permissionAwareActivity == null) {
|
|
373
|
+
promise.reject(E_NO_ACTIVITY, "Activity is not PermissionAwareActivity")
|
|
374
|
+
pickImagesPromise = null
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
val permissionsToRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && permission == Manifest.permission.READ_MEDIA_IMAGES) {
|
|
379
|
+
if (ContextCompat.checkSelfPermission(fragmentActivity, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) == PackageManager.PERMISSION_GRANTED) {
|
|
380
|
+
showSheet()
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
arrayOf(Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
|
|
384
|
+
} else {
|
|
385
|
+
arrayOf(permission)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
permissionAwareActivity.requestPermissions(permissionsToRequest, 100, object : PermissionListener {
|
|
389
|
+
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray): Boolean {
|
|
390
|
+
if (requestCode == 100 && grantResults != null) {
|
|
391
|
+
val isGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
392
|
+
grantResults.isNotEmpty() && (
|
|
393
|
+
grantResults[0] == PackageManager.PERMISSION_GRANTED ||
|
|
394
|
+
(grantResults.size > 1 && grantResults[1] == PackageManager.PERMISSION_GRANTED)
|
|
395
|
+
)
|
|
396
|
+
} else {
|
|
397
|
+
grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (isGranted) {
|
|
401
|
+
showSheet()
|
|
402
|
+
} else {
|
|
403
|
+
pickImagesPromise?.reject("E_PERMISSION_DENIED", "Permission denied")
|
|
404
|
+
pickImagesPromise = null
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return true
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
override fun openViewer(options: ReadableMap, promise: Promise) {
|
|
414
|
+
val activity = getCurrentActivity()
|
|
415
|
+
if (activity == null) {
|
|
416
|
+
promise.reject(E_NO_ACTIVITY, "Activity doesn't exist")
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (!options.hasKey("images")) {
|
|
421
|
+
promise.reject(E_FAILED_TO_PICK, "images array is required")
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
val imagesArray = options.getArray("images")
|
|
426
|
+
if (imagesArray == null || imagesArray.size() == 0) {
|
|
427
|
+
promise.reject(E_FAILED_TO_PICK, "images array cannot be empty")
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
val images = ArrayList<String>()
|
|
432
|
+
for (i in 0 until imagesArray.size()) {
|
|
433
|
+
val str = imagesArray.getString(i)
|
|
434
|
+
// ⚠️ null을 그냥 건너뛰면(add를 안 하면) 그 뒤 모든 이미지의 인덱스가 하나씩
|
|
435
|
+
// 앞으로 당겨져서, initialIndex/placeholderImages와 어긋나는 "인덱스 밀림" 버그가
|
|
436
|
+
// 됩니다. 아래 placeholderImages 루프처럼 빈 문자열로라도 자리를 채워 인덱스를
|
|
437
|
+
// 보존합니다.
|
|
438
|
+
images.add(str ?: "")
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// cacheFiles: images 와 같은 순서의 앱 캐시 파일 경로 (null → 빈 문자열). 뷰어 전체(모든 방식)에 적용.
|
|
442
|
+
val cacheFiles = ArrayList<String>()
|
|
443
|
+
if (options.hasKey("cacheFiles") && !options.isNull("cacheFiles")) {
|
|
444
|
+
val arr = options.getArray("cacheFiles")
|
|
445
|
+
if (arr != null) {
|
|
446
|
+
for (i in 0 until arr.size()) {
|
|
447
|
+
cacheFiles.add(if (arr.isNull(i)) "" else (arr.getString(i) ?: ""))
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
ViewerCacheFiles.register(images, if (cacheFiles.isEmpty()) null else cacheFiles)
|
|
452
|
+
|
|
453
|
+
val initialIndex = if (options.hasKey("initialIndex")) options.getInt("initialIndex") else 0
|
|
454
|
+
val themeColor = if (options.hasKey("themeColor")) options.getString("themeColor") else "#FF6B35"
|
|
455
|
+
val title = if (options.hasKey("title")) options.getString("title") else null
|
|
456
|
+
val animationType = if (options.hasKey("animationType")) options.getString("animationType") else null
|
|
457
|
+
// ⚠️ 뷰어가 항상 영어로만 나오던 원인: JS는 init()에서 설정한 기본 언어를 openViewer 옵션의
|
|
458
|
+
// languageCode로 매번 같이 넘기는데, 안드로이드 쪽에서는 그 값을 한 번도 읽지 않았고
|
|
459
|
+
// ImageViewerOverlayView는 TurboImagePickerConfig.attachBaseContext()(전역 languageCode,
|
|
460
|
+
// 기본값 "en")로 로케일을 강제하고 있었습니다. 여기서 반영해야 뷰어의 모든 문구가
|
|
461
|
+
// 설정된 언어를 따릅니다.
|
|
462
|
+
val viewerLanguageCode = if (options.hasKey("languageCode")) (options.getString("languageCode") ?: "en") else "en"
|
|
463
|
+
TurboImagePickerConfig.languageCode = viewerLanguageCode
|
|
464
|
+
|
|
465
|
+
var startX = -1f
|
|
466
|
+
var startY = -1f
|
|
467
|
+
var startWidth = -1f
|
|
468
|
+
var startHeight = -1f
|
|
469
|
+
|
|
470
|
+
if (options.hasKey("sourceRect")) {
|
|
471
|
+
val sourceRect = options.getMap("sourceRect")
|
|
472
|
+
if (sourceRect != null) {
|
|
473
|
+
startX = if (sourceRect.hasKey("x")) com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(sourceRect.getDouble("x").toFloat()) else -1f
|
|
474
|
+
startY = if (sourceRect.hasKey("y")) com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(sourceRect.getDouble("y").toFloat()) else -1f
|
|
475
|
+
startWidth = if (sourceRect.hasKey("width")) com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(sourceRect.getDouble("width").toFloat()) else -1f
|
|
476
|
+
startHeight = if (sourceRect.hasKey("height")) com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(sourceRect.getDouble("height").toFloat()) else -1f
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
val useDialogViewer = options.hasKey("useDialogViewer") && options.getBoolean("useDialogViewer")
|
|
481
|
+
val useOverlayViewer = options.hasKey("useOverlayViewer") && options.getBoolean("useOverlayViewer")
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
if (useOverlayViewer) {
|
|
485
|
+
val args = android.os.Bundle().apply {
|
|
486
|
+
putStringArrayList(ImageViewerActivity.EXTRA_IMAGES, images)
|
|
487
|
+
putInt(ImageViewerActivity.EXTRA_INITIAL_INDEX, initialIndex)
|
|
488
|
+
putString("EXTRA_THEME_COLOR", themeColor)
|
|
489
|
+
if (title != null) {
|
|
490
|
+
putString(ImageViewerActivity.EXTRA_TITLE, title)
|
|
491
|
+
}
|
|
492
|
+
if (animationType != null) {
|
|
493
|
+
putString("animationType", animationType)
|
|
494
|
+
}
|
|
495
|
+
if (options.hasKey("closeAnimationType")) {
|
|
496
|
+
putString("closeAnimationType", options.getString("closeAnimationType"))
|
|
497
|
+
}
|
|
498
|
+
if (startX != -1f) {
|
|
499
|
+
putFloat("startX", startX)
|
|
500
|
+
putFloat("startY", startY)
|
|
501
|
+
putFloat("startWidth", startWidth)
|
|
502
|
+
putFloat("startHeight", startHeight)
|
|
503
|
+
}
|
|
504
|
+
if (options.hasKey("sourceBorderRadius")) {
|
|
505
|
+
putFloat("sourceBorderRadius", com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(options.getDouble("sourceBorderRadius").toFloat()))
|
|
506
|
+
}
|
|
507
|
+
if (options.hasKey("sourceBorderCorners")) {
|
|
508
|
+
val cornersArray = options.getArray("sourceBorderCorners")
|
|
509
|
+
if (cornersArray != null) {
|
|
510
|
+
val corners = ArrayList<String>()
|
|
511
|
+
for (i in 0 until cornersArray.size()) {
|
|
512
|
+
cornersArray.getString(i)?.let { corners.add(it) }
|
|
513
|
+
}
|
|
514
|
+
putStringArrayList("sourceBorderCorners", corners)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// sourceBackgroundColor가 그동안 어느 뷰어 분기에서도 전달되지 않아
|
|
518
|
+
// mMaskView(닫기/열기 애니메이션 중 원본 썸네일을 가리는 마스크)가 항상 생성되지
|
|
519
|
+
// 않던 문제를 수정: JS의 sourceBackgroundColor(hex 문자열)를 그대로 전달합니다.
|
|
520
|
+
if (options.hasKey("sourceBackgroundColor")) {
|
|
521
|
+
putString("sourceBackgroundColor", options.getString("sourceBackgroundColor"))
|
|
522
|
+
}
|
|
523
|
+
// placeholderImages: 타입/뷰어 코드에는 있었지만 브릿지에서 한 번도 전달되지 않았던
|
|
524
|
+
// 필드. 네이티브 뷰어가 onlyRetrieveFromCache(true)로 첫 프레임을 즉시 그리려 할 때
|
|
525
|
+
// 쓰는 저해상도 대체 이미지 목록입니다(원본 다운로드 전 임시 표시용).
|
|
526
|
+
if (options.hasKey("placeholderImages")) {
|
|
527
|
+
val placeholderArray = options.getArray("placeholderImages")
|
|
528
|
+
if (placeholderArray != null) {
|
|
529
|
+
val placeholders = ArrayList<String>()
|
|
530
|
+
for (i in 0 until placeholderArray.size()) {
|
|
531
|
+
placeholders.add(placeholderArray.getString(i) ?: "")
|
|
532
|
+
}
|
|
533
|
+
putStringArrayList("placeholderImages", placeholders)
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// 상단바 저장/공유/삭제 버튼과 그 문구들 (iOS와 동일한 옵션 키). 문구를 안 넘기면
|
|
537
|
+
// 네이티브 쪽 로케일 리소스(values-ko/ja/zh, 그 외 en)가 자동 적용됩니다.
|
|
538
|
+
putBoolean("showActionButtons", !options.hasKey("showActionButtons") || options.getBoolean("showActionButtons"))
|
|
539
|
+
for (key in listOf(
|
|
540
|
+
"saveAllText", "saveOneText", "shareAllText", "shareOneText", "actionCancelText",
|
|
541
|
+
"deleteConfirmTitle", "deleteConfirmMessage", "deleteConfirmGroupMessage", "deleteButtonText"
|
|
542
|
+
)) {
|
|
543
|
+
if (options.hasKey(key)) {
|
|
544
|
+
options.getString(key)?.let { putString(key, it) }
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// patch405: 삭제 방식 선택지 [{ key, label, destructive }] → 나란한 배열 3개로 전달
|
|
548
|
+
if (options.hasKey("deleteOptions") && !options.isNull("deleteOptions")) {
|
|
549
|
+
val arr = options.getArray("deleteOptions")
|
|
550
|
+
if (arr != null) {
|
|
551
|
+
val keys = ArrayList<String>()
|
|
552
|
+
val labels = ArrayList<String>()
|
|
553
|
+
val destructive = ArrayList<Boolean>()
|
|
554
|
+
for (i in 0 until arr.size()) {
|
|
555
|
+
val m = arr.getMap(i) ?: continue
|
|
556
|
+
val k = if (m.hasKey("key")) m.getString("key") else null
|
|
557
|
+
val l = if (m.hasKey("label")) m.getString("label") else null
|
|
558
|
+
if (k.isNullOrEmpty() || l.isNullOrEmpty()) continue
|
|
559
|
+
keys.add(k)
|
|
560
|
+
labels.add(l)
|
|
561
|
+
destructive.add(m.hasKey("destructive") && !m.isNull("destructive") && m.getBoolean("destructive"))
|
|
562
|
+
}
|
|
563
|
+
if (keys.isNotEmpty()) {
|
|
564
|
+
putStringArrayList("deleteOptionKeys", keys)
|
|
565
|
+
putStringArrayList("deleteOptionLabels", labels)
|
|
566
|
+
putBooleanArray("deleteOptionDestructive", destructive.toBooleanArray())
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// 이전 세션에서 남은 sticky 좌표가 이번 오픈에 잘못 적용되지 않도록 초기화.
|
|
572
|
+
ImageViewerOverlayView.clearPendingCoordinates()
|
|
573
|
+
activity.runOnUiThread {
|
|
574
|
+
ImageViewerOverlayView.present(activity, args)
|
|
575
|
+
}
|
|
576
|
+
promise.resolve(null)
|
|
577
|
+
return
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (useDialogViewer && activity is androidx.fragment.app.FragmentActivity) {
|
|
581
|
+
val args = android.os.Bundle().apply {
|
|
582
|
+
putStringArrayList(ImageViewerActivity.EXTRA_IMAGES, images)
|
|
583
|
+
putInt(ImageViewerActivity.EXTRA_INITIAL_INDEX, initialIndex)
|
|
584
|
+
putString("EXTRA_THEME_COLOR", themeColor)
|
|
585
|
+
if (title != null) {
|
|
586
|
+
putString(ImageViewerActivity.EXTRA_TITLE, title)
|
|
587
|
+
}
|
|
588
|
+
if (animationType != null) {
|
|
589
|
+
putString("animationType", animationType)
|
|
590
|
+
}
|
|
591
|
+
if (options.hasKey("closeAnimationType")) {
|
|
592
|
+
putString("closeAnimationType", options.getString("closeAnimationType"))
|
|
593
|
+
}
|
|
594
|
+
if (startX != -1f) {
|
|
595
|
+
putFloat("startX", startX)
|
|
596
|
+
putFloat("startY", startY)
|
|
597
|
+
putFloat("startWidth", startWidth)
|
|
598
|
+
putFloat("startHeight", startHeight)
|
|
599
|
+
}
|
|
600
|
+
if (options.hasKey("sourceBorderRadius")) {
|
|
601
|
+
putFloat("sourceBorderRadius", com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(options.getDouble("sourceBorderRadius").toFloat()))
|
|
602
|
+
}
|
|
603
|
+
if (options.hasKey("sourceBorderCorners")) {
|
|
604
|
+
val cornersArray = options.getArray("sourceBorderCorners")
|
|
605
|
+
if (cornersArray != null) {
|
|
606
|
+
val corners = ArrayList<String>()
|
|
607
|
+
for (i in 0 until cornersArray.size()) {
|
|
608
|
+
cornersArray.getString(i)?.let { corners.add(it) }
|
|
609
|
+
}
|
|
610
|
+
putStringArrayList("sourceBorderCorners", corners)
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
// sourceBackgroundColor가 그동안 어느 뷰어 분기에서도 전달되지 않아
|
|
614
|
+
// mMaskView(닫기/열기 애니메이션 중 원본 썸네일을 가리는 마스크)가 항상 생성되지
|
|
615
|
+
// 않던 문제를 수정: JS의 sourceBackgroundColor(hex 문자열)를 그대로 전달합니다.
|
|
616
|
+
if (options.hasKey("sourceBackgroundColor")) {
|
|
617
|
+
putString("sourceBackgroundColor", options.getString("sourceBackgroundColor"))
|
|
618
|
+
}
|
|
619
|
+
// placeholderImages: 타입/뷰어 코드에는 있었지만 브릿지에서 한 번도 전달되지 않았던
|
|
620
|
+
// 필드. 네이티브 뷰어가 onlyRetrieveFromCache(true)로 첫 프레임을 즉시 그리려 할 때
|
|
621
|
+
// 쓰는 저해상도 대체 이미지 목록입니다(원본 다운로드 전 임시 표시용).
|
|
622
|
+
if (options.hasKey("placeholderImages")) {
|
|
623
|
+
val placeholderArray = options.getArray("placeholderImages")
|
|
624
|
+
if (placeholderArray != null) {
|
|
625
|
+
val placeholders = ArrayList<String>()
|
|
626
|
+
for (i in 0 until placeholderArray.size()) {
|
|
627
|
+
placeholders.add(placeholderArray.getString(i) ?: "")
|
|
628
|
+
}
|
|
629
|
+
putStringArrayList("placeholderImages", placeholders)
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// 이전 세션에서 남은 sticky 좌표가 이번 오픈에 잘못 적용되지 않도록 초기화.
|
|
634
|
+
ImageViewerDialogFragment.clearPendingCoordinates()
|
|
635
|
+
activity.runOnUiThread {
|
|
636
|
+
ImageViewerDialogFragment.newInstance(args)
|
|
637
|
+
.show(activity.supportFragmentManager, "image_viewer_dialog")
|
|
638
|
+
}
|
|
639
|
+
promise.resolve(null)
|
|
640
|
+
return
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
val intent = Intent(activity, ImageViewerActivity::class.java).apply {
|
|
644
|
+
putStringArrayListExtra(ImageViewerActivity.EXTRA_IMAGES, images)
|
|
645
|
+
putExtra(ImageViewerActivity.EXTRA_INITIAL_INDEX, initialIndex)
|
|
646
|
+
putExtra("EXTRA_THEME_COLOR", themeColor)
|
|
647
|
+
if (title != null) {
|
|
648
|
+
putExtra(ImageViewerActivity.EXTRA_TITLE, title)
|
|
649
|
+
}
|
|
650
|
+
if (animationType != null) {
|
|
651
|
+
putExtra("animationType", animationType)
|
|
652
|
+
}
|
|
653
|
+
if (options.hasKey("closeAnimationType")) {
|
|
654
|
+
putExtra("closeAnimationType", options.getString("closeAnimationType"))
|
|
655
|
+
}
|
|
656
|
+
if (startX != -1f) {
|
|
657
|
+
putExtra("startX", startX)
|
|
658
|
+
putExtra("startY", startY)
|
|
659
|
+
putExtra("startWidth", startWidth)
|
|
660
|
+
putExtra("startHeight", startHeight)
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (options.hasKey("sourceBorderRadius")) {
|
|
664
|
+
putExtra("sourceBorderRadius", com.facebook.react.uimanager.PixelUtil.toPixelFromDIP(options.getDouble("sourceBorderRadius").toFloat()))
|
|
665
|
+
}
|
|
666
|
+
if (options.hasKey("sourceBorderCorners")) {
|
|
667
|
+
val cornersArray = options.getArray("sourceBorderCorners")
|
|
668
|
+
if (cornersArray != null) {
|
|
669
|
+
val corners = ArrayList<String>()
|
|
670
|
+
for (i in 0 until cornersArray.size()) {
|
|
671
|
+
cornersArray.getString(i)?.let { corners.add(it) }
|
|
672
|
+
}
|
|
673
|
+
putStringArrayListExtra("sourceBorderCorners", corners)
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (options.hasKey("sourceBackgroundColor")) {
|
|
677
|
+
putExtra("sourceBackgroundColor", options.getString("sourceBackgroundColor"))
|
|
678
|
+
}
|
|
679
|
+
if (options.hasKey("placeholderImages")) {
|
|
680
|
+
val placeholderArray = options.getArray("placeholderImages")
|
|
681
|
+
if (placeholderArray != null) {
|
|
682
|
+
val placeholders = ArrayList<String>()
|
|
683
|
+
for (i in 0 until placeholderArray.size()) {
|
|
684
|
+
placeholders.add(placeholderArray.getString(i) ?: "")
|
|
685
|
+
}
|
|
686
|
+
putStringArrayListExtra("placeholderImages", placeholders)
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
activity.startActivity(intent)
|
|
691
|
+
promise.resolve(null)
|
|
692
|
+
} catch (e: Exception) {
|
|
693
|
+
promise.reject(E_FAILED_TO_PICK, "Failed to open image viewer: ${e.message}", e)
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
override fun closeGallery(promise: Promise) {
|
|
698
|
+
val activity = getCurrentActivity()
|
|
699
|
+
if (activity == null) {
|
|
700
|
+
promise.reject(E_NO_ACTIVITY, "Activity doesn't exist")
|
|
701
|
+
return
|
|
702
|
+
}
|
|
703
|
+
activity.finishActivity(REQUEST_CODE_IMAGE_PICKER)
|
|
704
|
+
promise.resolve(true)
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
override fun addListener(eventName: String) {
|
|
708
|
+
// Keep: Required for RN built-in Event Emitter Calls.
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
override fun removeListeners(count: Double) {
|
|
712
|
+
// Keep: Required for RN built-in Event Emitter Calls.
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
override fun onActivityResult(
|
|
716
|
+
activity: Activity,
|
|
717
|
+
requestCode: Int,
|
|
718
|
+
resultCode: Int,
|
|
719
|
+
data: Intent?
|
|
720
|
+
) {
|
|
721
|
+
if (requestCode == REQUEST_CODE_IMAGE_EDITOR) {
|
|
722
|
+
val promise = editImagePromise ?: return
|
|
723
|
+
editImagePromise = null
|
|
724
|
+
|
|
725
|
+
if (resultCode != Activity.RESULT_OK || data == null) {
|
|
726
|
+
promise.reject(E_PICKER_CANCELLED, "User cancelled image editor")
|
|
727
|
+
return
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
val uriStrings = data.getStringArrayListExtra(ImageEditorActivity.EXTRA_RESULT_URIS)
|
|
731
|
+
if (uriStrings == null || uriStrings.isEmpty()) {
|
|
732
|
+
promise.reject(E_PICKER_CANCELLED, "User cancelled image editor")
|
|
733
|
+
return
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
val uris = uriStrings.mapNotNull { uriString ->
|
|
737
|
+
try { Uri.parse(uriString) } catch (e: Exception) { null }
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (uris.isEmpty()) {
|
|
741
|
+
promise.reject(E_FAILED_TO_PICK, "Failed to parse image URIs")
|
|
742
|
+
return
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
moduleScope.launch {
|
|
746
|
+
try {
|
|
747
|
+
val results = processImages(uris)
|
|
748
|
+
if (results.isNotEmpty()) {
|
|
749
|
+
promise.resolve(results[0])
|
|
750
|
+
} else {
|
|
751
|
+
promise.reject(E_FAILED_TO_PICK, "Failed to process image")
|
|
752
|
+
}
|
|
753
|
+
} catch (e: Exception) {
|
|
754
|
+
promise.reject(E_FAILED_TO_PICK, "Failed to process image: ${e.message}", e)
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
override fun onNewIntent(intent: Intent) {
|
|
764
|
+
// Not needed for this module
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
private fun handleSelectedImages(uriStrings: ReadableArray, promise: Promise?) {
|
|
768
|
+
val actualPromise = promise ?: return
|
|
769
|
+
|
|
770
|
+
// Convert string URIs to Uri objects
|
|
771
|
+
val uris = mutableListOf<Uri>()
|
|
772
|
+
for (i in 0 until uriStrings.size()) {
|
|
773
|
+
val uriString = uriStrings.getString(i)
|
|
774
|
+
if (uriString != null) {
|
|
775
|
+
try {
|
|
776
|
+
uris.add(Uri.parse(uriString))
|
|
777
|
+
} catch (e: Exception) {
|
|
778
|
+
// Ignore
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (uris.isEmpty()) {
|
|
784
|
+
actualPromise.reject(E_FAILED_TO_PICK, "Failed to parse image URIs")
|
|
785
|
+
return
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Process images asynchronously
|
|
789
|
+
val eventMap = com.facebook.react.bridge.WritableNativeMap()
|
|
790
|
+
eventMap.putInt("selectedCount", uris.size)
|
|
791
|
+
eventMap.putInt("maxSelection", 10)
|
|
792
|
+
try {
|
|
793
|
+
reactApplicationContext.getJSModule(com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
794
|
+
.emit("onSelectionChange", eventMap)
|
|
795
|
+
} catch (e: Exception) {
|
|
796
|
+
// Ignore
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
moduleScope.launch {
|
|
800
|
+
try {
|
|
801
|
+
val results = processImages(uris)
|
|
802
|
+
val array = com.facebook.react.bridge.Arguments.createArray()
|
|
803
|
+
results.forEach { array.pushMap(it) }
|
|
804
|
+
actualPromise.resolve(array)
|
|
805
|
+
} catch (e: Exception) {
|
|
806
|
+
actualPromise.reject(E_FAILED_TO_PICK, "Failed to process images: ${e.message}", e)
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
private suspend fun processImages(uris: List<Uri>): List<com.facebook.react.bridge.WritableMap> = withContext(Dispatchers.IO) {
|
|
812
|
+
val results = mutableListOf<com.facebook.react.bridge.WritableMap>()
|
|
813
|
+
val contentResolver = reactApplicationContext.contentResolver
|
|
814
|
+
val totalCount = uris.size
|
|
815
|
+
var currentIndex = 0
|
|
816
|
+
|
|
817
|
+
for (uri in uris) {
|
|
818
|
+
try {
|
|
819
|
+
// 1. Get EXIF Orientation
|
|
820
|
+
var orientation = android.media.ExifInterface.ORIENTATION_NORMAL
|
|
821
|
+
try {
|
|
822
|
+
contentResolver.openInputStream(uri)?.use { exifInputStream ->
|
|
823
|
+
val exif = android.media.ExifInterface(exifInputStream)
|
|
824
|
+
orientation = exif.getAttributeInt(android.media.ExifInterface.TAG_ORIENTATION, android.media.ExifInterface.ORIENTATION_NORMAL)
|
|
825
|
+
}
|
|
826
|
+
} catch (e: Exception) {}
|
|
827
|
+
|
|
828
|
+
contentResolver.openInputStream(uri)?.use { inputStream ->
|
|
829
|
+
// Get original image dimensions
|
|
830
|
+
val originalOptions = BitmapFactory.Options().apply {
|
|
831
|
+
inJustDecodeBounds = true
|
|
832
|
+
}
|
|
833
|
+
BitmapFactory.decodeStream(inputStream, null, originalOptions)
|
|
834
|
+
|
|
835
|
+
var originalWidth = originalOptions.outWidth
|
|
836
|
+
var originalHeight = originalOptions.outHeight
|
|
837
|
+
|
|
838
|
+
// Swap width and height if rotated by 90 or 270 degrees
|
|
839
|
+
if (orientation == android.media.ExifInterface.ORIENTATION_ROTATE_90 || orientation == android.media.ExifInterface.ORIENTATION_ROTATE_270) {
|
|
840
|
+
val temp = originalWidth
|
|
841
|
+
originalWidth = originalHeight
|
|
842
|
+
originalHeight = temp
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Load and resize image
|
|
846
|
+
contentResolver.openInputStream(uri)?.use { resizedInputStream ->
|
|
847
|
+
// Initial decode for sizing
|
|
848
|
+
val decodeOptions = BitmapFactory.Options().apply {
|
|
849
|
+
inSampleSize = calculateInSampleSize(originalWidth, originalHeight, maxWidth, maxHeight)
|
|
850
|
+
}
|
|
851
|
+
var bitmap = BitmapFactory.decodeStream(resizedInputStream, null, decodeOptions)
|
|
852
|
+
|
|
853
|
+
if (bitmap != null) {
|
|
854
|
+
// Apply EXIF Rotation
|
|
855
|
+
val matrix = android.graphics.Matrix()
|
|
856
|
+
when (orientation) {
|
|
857
|
+
android.media.ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
|
|
858
|
+
android.media.ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
|
|
859
|
+
android.media.ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
|
|
860
|
+
android.media.ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.postScale(-1f, 1f)
|
|
861
|
+
android.media.ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.postScale(1f, -1f)
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (!matrix.isIdentity) {
|
|
865
|
+
val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
|
866
|
+
if (rotatedBitmap != bitmap) {
|
|
867
|
+
bitmap.recycle()
|
|
868
|
+
bitmap = rotatedBitmap
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Precise resize if needed
|
|
873
|
+
val finalBitmap = if (bitmap.width > maxWidth || bitmap.height > maxHeight) {
|
|
874
|
+
resizeBitmap(bitmap, maxWidth, maxHeight)
|
|
875
|
+
} else {
|
|
876
|
+
bitmap
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
val processedBitmap = ImageProcessor.applyWatermarkIfNeeded(reactApplicationContext, finalBitmap)
|
|
880
|
+
|
|
881
|
+
// Determine format and extension
|
|
882
|
+
val compressFormat: Bitmap.CompressFormat
|
|
883
|
+
val extension: String
|
|
884
|
+
val mimeType: String
|
|
885
|
+
|
|
886
|
+
when (outputFormat) {
|
|
887
|
+
"png" -> {
|
|
888
|
+
compressFormat = Bitmap.CompressFormat.PNG
|
|
889
|
+
extension = "png"
|
|
890
|
+
mimeType = "image/png"
|
|
891
|
+
}
|
|
892
|
+
"jpg", "jpeg" -> {
|
|
893
|
+
compressFormat = Bitmap.CompressFormat.JPEG
|
|
894
|
+
extension = "jpg"
|
|
895
|
+
mimeType = "image/jpeg"
|
|
896
|
+
}
|
|
897
|
+
else -> { // webp
|
|
898
|
+
compressFormat = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
|
899
|
+
Bitmap.CompressFormat.WEBP_LOSSY
|
|
900
|
+
} else {
|
|
901
|
+
@Suppress("DEPRECATION")
|
|
902
|
+
Bitmap.CompressFormat.WEBP
|
|
903
|
+
}
|
|
904
|
+
extension = "webp"
|
|
905
|
+
mimeType = "image/webp"
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Save to temporary file
|
|
910
|
+
val tempFile = File.createTempFile("turbo_edited_${System.currentTimeMillis()}_", ".$extension", reactApplicationContext.cacheDir)
|
|
911
|
+
FileOutputStream(tempFile).use { out ->
|
|
912
|
+
processedBitmap.compress(compressFormat, 85, out)
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
val finalWidth = processedBitmap.width
|
|
916
|
+
val finalHeight = processedBitmap.height
|
|
917
|
+
|
|
918
|
+
// Cleanup
|
|
919
|
+
if (bitmap != processedBitmap) {
|
|
920
|
+
bitmap.recycle()
|
|
921
|
+
}
|
|
922
|
+
if (finalBitmap != processedBitmap && finalBitmap != bitmap) {
|
|
923
|
+
finalBitmap.recycle()
|
|
924
|
+
}
|
|
925
|
+
processedBitmap.recycle()
|
|
926
|
+
|
|
927
|
+
val createImageInfo = {
|
|
928
|
+
WritableNativeMap().apply {
|
|
929
|
+
putString("uri", Uri.fromFile(tempFile).toString())
|
|
930
|
+
putInt("width", finalWidth)
|
|
931
|
+
putInt("height", finalHeight)
|
|
932
|
+
putString("type", mimeType)
|
|
933
|
+
putString("fileName", tempFile.name)
|
|
934
|
+
putString("fileExtension", extension)
|
|
935
|
+
putDouble("fileSize", tempFile.length().toDouble())
|
|
936
|
+
putString("originalUri", uri.toString())
|
|
937
|
+
putInt("originalWidth", originalWidth)
|
|
938
|
+
putInt("originalHeight", originalHeight)
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
val imageInfo = createImageInfo()
|
|
942
|
+
results.add(imageInfo)
|
|
943
|
+
|
|
944
|
+
try {
|
|
945
|
+
val eventMap = WritableNativeMap().apply {
|
|
946
|
+
putInt("total", totalCount)
|
|
947
|
+
putInt("index", currentIndex)
|
|
948
|
+
putMap("image", createImageInfo())
|
|
949
|
+
}
|
|
950
|
+
reactApplicationContext.getJSModule(com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
951
|
+
.emit("onImageProcessed", eventMap)
|
|
952
|
+
} catch (e: Exception) { }
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
currentIndex++
|
|
957
|
+
} catch (e: Exception) {
|
|
958
|
+
currentIndex++
|
|
959
|
+
// Skip failed images
|
|
960
|
+
continue
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
results
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
private fun calculateInSampleSize(
|
|
968
|
+
reqWidth: Int,
|
|
969
|
+
reqHeight: Int,
|
|
970
|
+
maxWidth: Int,
|
|
971
|
+
maxHeight: Int
|
|
972
|
+
): Int {
|
|
973
|
+
var inSampleSize = 1
|
|
974
|
+
|
|
975
|
+
if (reqHeight > maxHeight || reqWidth > maxWidth) {
|
|
976
|
+
val halfHeight = reqHeight / 2
|
|
977
|
+
val halfWidth = reqWidth / 2
|
|
978
|
+
|
|
979
|
+
while ((halfHeight / inSampleSize) >= maxHeight && (halfWidth / inSampleSize) >= maxWidth) {
|
|
980
|
+
inSampleSize *= 2
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return inSampleSize
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
private fun resizeBitmap(bitmap: Bitmap, maxWidth: Int, maxHeight: Int): Bitmap {
|
|
988
|
+
val width = bitmap.width
|
|
989
|
+
val height = bitmap.height
|
|
990
|
+
|
|
991
|
+
val ratioBitmap = width.toFloat() / height.toFloat()
|
|
992
|
+
val ratioMax = maxWidth.toFloat() / maxHeight.toFloat()
|
|
993
|
+
|
|
994
|
+
var finalWidth = maxWidth
|
|
995
|
+
var finalHeight = maxHeight
|
|
996
|
+
|
|
997
|
+
if (ratioMax > ratioBitmap) {
|
|
998
|
+
finalWidth = (maxHeight * ratioBitmap).toInt()
|
|
999
|
+
} else {
|
|
1000
|
+
finalHeight = (maxWidth / ratioBitmap).toInt()
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return Bitmap.createScaledBitmap(bitmap, finalWidth, finalHeight, true)
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
// MARK: - Media gallery (사진 모아보기)
|
|
1008
|
+
|
|
1009
|
+
/** 열려 있는 미디어 갤러리 (galleryId → 화면). 메인 스레드에서만 접근 */
|
|
1010
|
+
private val mediaGalleries = HashMap<String, MediaGalleryOverlay>()
|
|
1011
|
+
|
|
1012
|
+
private fun emitGalleryEvent(name: String, body: WritableMap) {
|
|
1013
|
+
try {
|
|
1014
|
+
reactApplicationContext
|
|
1015
|
+
.getJSModule(com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
1016
|
+
.emit(name, body)
|
|
1017
|
+
} catch (e: Exception) {
|
|
1018
|
+
android.util.Log.w("TurboMediaGallery", "emit $name failed", e)
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* openMediaGallery(options) → { galleryId }
|
|
1024
|
+
* - dataSource: 등록된 네이티브 데이터 소스 이름 (MediaGalleryRegistry) — 페이지 요청이 JS 를 거치지 않음
|
|
1025
|
+
* - initialItems / nextCursor / hasMore: JS 공급 방식 (끝에 가까우면 onGalleryNeedPage → appendGalleryItems)
|
|
1026
|
+
* Activity 를 띄우지 않고 호스트 Activity 위에 View 로 얹습니다.
|
|
1027
|
+
*/
|
|
1028
|
+
override fun openMediaGallery(options: ReadableMap, promise: Promise) {
|
|
1029
|
+
val activity = getCurrentActivity()
|
|
1030
|
+
if (activity == null) {
|
|
1031
|
+
promise.reject(E_NO_ACTIVITY, "Activity doesn't exist")
|
|
1032
|
+
return
|
|
1033
|
+
}
|
|
1034
|
+
val o: Map<String, Any?> = options.toHashMap()
|
|
1035
|
+
com.facebook.react.bridge.UiThreadUtil.runOnUiThread {
|
|
1036
|
+
try {
|
|
1037
|
+
val systemDark = (activity.resources.configuration.uiMode and
|
|
1038
|
+
android.content.res.Configuration.UI_MODE_NIGHT_MASK) == android.content.res.Configuration.UI_MODE_NIGHT_YES
|
|
1039
|
+
val config = MediaGalleryConfig(o, systemDark)
|
|
1040
|
+
val galleryId = config.galleryId
|
|
1041
|
+
if (mediaGalleries[galleryId]?.isShowing == true) {
|
|
1042
|
+
promise.reject("E_GALLERY_OPEN", "Media gallery '$galleryId' is already open")
|
|
1043
|
+
return@runOnUiThread
|
|
1044
|
+
}
|
|
1045
|
+
MediaGalleryRegistry.ensureDemo(activity.applicationContext)
|
|
1046
|
+
val dataSourceName = o["dataSource"] as? String
|
|
1047
|
+
val store: MediaGalleryStore
|
|
1048
|
+
if (!dataSourceName.isNullOrEmpty()) {
|
|
1049
|
+
val dataSource = MediaGalleryRegistry.source(dataSourceName)
|
|
1050
|
+
if (dataSource == null) {
|
|
1051
|
+
promise.reject("E_NO_DATA_SOURCE", "Media gallery data source '$dataSourceName' is not registered")
|
|
1052
|
+
return@runOnUiThread
|
|
1053
|
+
}
|
|
1054
|
+
@Suppress("UNCHECKED_CAST")
|
|
1055
|
+
val params = (o["dataSourceParams"] as? Map<String, Any?>) ?: emptyMap()
|
|
1056
|
+
store = MediaGalleryStore(galleryId, MediaGalleryStore.Source.Native(dataSource, params))
|
|
1057
|
+
} else {
|
|
1058
|
+
store = MediaGalleryStore(
|
|
1059
|
+
galleryId,
|
|
1060
|
+
MediaGalleryStore.Source.JavaScript,
|
|
1061
|
+
MediaGalleryItem.listFrom(o["initialItems"]),
|
|
1062
|
+
o["nextCursor"] as? String,
|
|
1063
|
+
o["hasMore"] as? Boolean ?: false
|
|
1064
|
+
)
|
|
1065
|
+
store.onNeedPage = { cursor ->
|
|
1066
|
+
emitGalleryEvent("onGalleryNeedPage", Arguments.createMap().apply {
|
|
1067
|
+
putString("galleryId", galleryId)
|
|
1068
|
+
if (cursor != null) putString("cursor", cursor) else putNull("cursor")
|
|
1069
|
+
})
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
val overlay = MediaGalleryOverlay(activity, store, config)
|
|
1073
|
+
overlay.onClosed = {
|
|
1074
|
+
if (mediaGalleries[galleryId] === overlay) mediaGalleries.remove(galleryId)
|
|
1075
|
+
emitGalleryEvent("onGalleryClosed", Arguments.createMap().apply { putString("galleryId", galleryId) })
|
|
1076
|
+
}
|
|
1077
|
+
// 뷰어에서 삭제를 고르면: 모듈은 그 묶음을 그리드·뷰어에서 지우고, 실제 삭제는 앱이 하도록 이벤트만 보냄
|
|
1078
|
+
overlay.onItemDeleted = { item, option ->
|
|
1079
|
+
emitGalleryEvent("onGalleryItemDeleted", Arguments.createMap().apply {
|
|
1080
|
+
putString("galleryId", galleryId)
|
|
1081
|
+
putString("id", item.id)
|
|
1082
|
+
putString("groupId", item.groupId)
|
|
1083
|
+
if (option != null) putString("option", option) else putNull("option")
|
|
1084
|
+
})
|
|
1085
|
+
}
|
|
1086
|
+
// 상단 바 메뉴에서 그리드 크기를 바꾸면 (모듈이 기기에 저장, 앱에도 알림)
|
|
1087
|
+
overlay.onGridSizeChanged = { columns ->
|
|
1088
|
+
emitGalleryEvent("onGalleryGridSizeChanged", Arguments.createMap().apply {
|
|
1089
|
+
putString("galleryId", galleryId)
|
|
1090
|
+
putString("gridSize", MediaGalleryGridPreference.gridSizeString(columns))
|
|
1091
|
+
})
|
|
1092
|
+
}
|
|
1093
|
+
mediaGalleries[galleryId] = overlay
|
|
1094
|
+
overlay.show()
|
|
1095
|
+
promise.resolve(Arguments.createMap().apply { putString("galleryId", galleryId) })
|
|
1096
|
+
} catch (e: Exception) {
|
|
1097
|
+
promise.reject("E_MEDIA_GALLERY", "Failed to open media gallery: ${e.message}", e)
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
override fun closeMediaGallery(galleryId: String, promise: Promise) {
|
|
1103
|
+
com.facebook.react.bridge.UiThreadUtil.runOnUiThread {
|
|
1104
|
+
mediaGalleries[galleryId]?.close(includeViewer = true)
|
|
1105
|
+
promise.resolve(null)
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/** JS 공급 방식: { galleryId, items, nextCursor?, hasMore } */
|
|
1110
|
+
override fun appendGalleryItems(payload: ReadableMap, promise: Promise) {
|
|
1111
|
+
val p: Map<String, Any?> = payload.toHashMap()
|
|
1112
|
+
val galleryId = p["galleryId"] as? String ?: ""
|
|
1113
|
+
val page = MediaGalleryPage(
|
|
1114
|
+
MediaGalleryItem.listFrom(p["items"]),
|
|
1115
|
+
p["nextCursor"] as? String,
|
|
1116
|
+
p["hasMore"] as? Boolean ?: false
|
|
1117
|
+
)
|
|
1118
|
+
com.facebook.react.bridge.UiThreadUtil.runOnUiThread {
|
|
1119
|
+
mediaGalleries[galleryId]?.store?.appendExternal(page)
|
|
1120
|
+
promise.resolve(null)
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/** 앱이 다른 경로로 지운 묶음 반영: { galleryId, groupIds } */
|
|
1125
|
+
override fun removeGalleryItems(payload: ReadableMap, promise: Promise) {
|
|
1126
|
+
val p: Map<String, Any?> = payload.toHashMap()
|
|
1127
|
+
val galleryId = p["galleryId"] as? String ?: ""
|
|
1128
|
+
val groupIds = (p["groupIds"] as? List<*> ?: emptyList<Any?>()).mapNotNull { v ->
|
|
1129
|
+
when (v) {
|
|
1130
|
+
is String -> v
|
|
1131
|
+
is Number -> v.toDouble().let { d -> if (d % 1.0 == 0.0) d.toLong().toString() else d.toString() }
|
|
1132
|
+
else -> null
|
|
1133
|
+
}
|
|
1134
|
+
}.toSet()
|
|
1135
|
+
com.facebook.react.bridge.UiThreadUtil.runOnUiThread {
|
|
1136
|
+
mediaGalleries[galleryId]?.store?.removeGroups(groupIds)
|
|
1137
|
+
promise.resolve(null)
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
override fun invalidate() {
|
|
1142
|
+
super.invalidate()
|
|
1143
|
+
com.facebook.react.bridge.UiThreadUtil.runOnUiThread {
|
|
1144
|
+
val open = mediaGalleries.values.toList()
|
|
1145
|
+
mediaGalleries.clear()
|
|
1146
|
+
open.forEach { it.dismissImmediately() }
|
|
1147
|
+
}
|
|
1148
|
+
reactApplicationContext.removeActivityEventListener(this)
|
|
1149
|
+
androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(reactApplicationContext)
|
|
1150
|
+
.unregisterReceiver(pageChangeReceiver)
|
|
1151
|
+
androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(reactApplicationContext)
|
|
1152
|
+
.unregisterReceiver(viewerOpenedReceiver)
|
|
1153
|
+
androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(reactApplicationContext)
|
|
1154
|
+
.unregisterReceiver(viewerWillCloseReceiver)
|
|
1155
|
+
androidx.localbroadcastmanager.content.LocalBroadcastManager.getInstance(reactApplicationContext)
|
|
1156
|
+
.unregisterReceiver(imageDeletedReceiver)
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|