Skip to content

Commit 9227ce5

Browse files
committed
✨ feat: add more option for crop, ratio, default ratio for both platform
1 parent bed75ea commit 9227ce5

23 files changed

+380
-110
lines changed

Diff for: android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePicker.kt

-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package com.margelo.nitro.multipleimagepicker
22

33
import com.margelo.nitro.NitroModules
4-
import com.margelo.nitro.multipleimagepicker.HybridMultipleImagePickerSpec
5-
import com.margelo.nitro.multipleimagepicker.NitroConfig
6-
import com.margelo.nitro.multipleimagepicker.Result
74

85

96
class MultipleImagePicker : HybridMultipleImagePickerSpec() {

Diff for: android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePickerImp.kt

+85-18
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.margelo.nitro.multipleimagepicker
22

3+
import android.app.Activity
34
import android.content.Context
5+
import android.content.Intent
46
import android.graphics.Color
57
import android.net.Uri
68
import androidx.core.content.ContextCompat
9+
import com.facebook.react.bridge.ActivityEventListener
10+
import com.facebook.react.bridge.BaseActivityEventListener
711
import com.facebook.react.bridge.ColorPropConverter
812
import com.facebook.react.bridge.ReactApplicationContext
913
import com.facebook.react.bridge.ReactContextBaseJavaModule
@@ -28,11 +32,13 @@ import com.luck.picture.lib.utils.DateUtils
2832
import com.luck.picture.lib.utils.DensityUtil
2933
import com.yalantis.ucrop.UCrop
3034
import com.yalantis.ucrop.UCrop.Options
35+
import com.yalantis.ucrop.UCrop.REQUEST_CROP
3136
import com.yalantis.ucrop.model.AspectRatio
3237
import java.io.File
3338
import java.net.HttpURLConnection
3439
import java.net.URL
3540

41+
3642
class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
3743
ReactContextBaseJavaModule(reactContext), IApp {
3844

@@ -91,7 +97,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
9197
PictureSelector.create(activity).openGallery(chooseMode).setImageEngine(imageEngine)
9298
.setSelectedData(dataList).setSelectorUIStyle(style).apply {
9399
if (isCrop) {
94-
setCropOption()
100+
setCropOption(config.crop)
95101
// Disabled force crop engine for multiple
96102
if (!isMultiple) setCropEngine(CropEngine(cropOption))
97103
else setEditMediaInterceptListener(setEditMediaEvent())
@@ -161,7 +167,16 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
161167
resolved: (result: CropResult) -> Unit,
162168
rejected: (reject: Double) -> Unit
163169
) {
164-
setCropOption()
170+
cropOption = Options()
171+
172+
setCropOption(
173+
PickerCropConfig(
174+
circle = options.circle,
175+
ratio = options.ratio,
176+
defaultRatio = options.defaultRatio,
177+
freeStyle = options.freeStyle
178+
)
179+
)
165180

166181
try {
167182
val uri = when {
@@ -189,19 +204,46 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
189204
}
190205
}
191206

207+
192208
val destinationUri = Uri.fromFile(
193209
File(getSandboxPath(appContext), DateUtils.getCreateFileName("CROP_") + ".jpeg")
194210
)
195211

196212
val uCrop = UCrop.of<Any>(uri, destinationUri).withOptions(cropOption)
197213

198-
199214
// set engine
200215
uCrop.setImageEngine(CropImageEngine())
201216
// start edit
202-
currentActivity?.let { uCrop.start(it, 0) }
203217

218+
val cropActivityEventListener = object : BaseActivityEventListener() {
219+
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
220+
if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CROP) {
221+
val resultUri = UCrop.getOutput(data!!)
222+
val width = UCrop.getOutputImageWidth(data).toDouble()
223+
val height = UCrop.getOutputImageHeight(data).toDouble()
224+
225+
resultUri?.let {
226+
val result = CropResult(
227+
path = it.toString(),
228+
width,
229+
height,
230+
)
231+
resolved(result)
232+
}
233+
} else if (resultCode == UCrop.RESULT_ERROR) {
234+
val cropError = UCrop.getError(data!!)
235+
rejected(0.0)
236+
}
237+
238+
// Remove listener after getting result
239+
reactApplicationContext.removeActivityEventListener(this)
240+
}
241+
}
242+
243+
// Add listener before starting UCrop
244+
reactApplicationContext.addActivityEventListener(cropActivityEventListener)
204245

246+
currentActivity?.let { uCrop.start(it, REQUEST_CROP) }
205247
} catch (e: Exception) {
206248
rejected(0.0)
207249
}
@@ -223,12 +265,10 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
223265
}
224266
}
225267

226-
private fun setCropOption() {
227-
// val mainStyle: SelectMainStyle = style.selectMainStyle
228-
268+
private fun setCropOption(config: PickerCropConfig?) {
229269
cropOption.setShowCropFrame(true)
230270
cropOption.setShowCropGrid(true)
231-
cropOption.setCircleDimmedLayer(config.crop?.circle ?: false)
271+
cropOption.setCircleDimmedLayer(config?.circle ?: false)
232272
cropOption.setCropOutputPathDir(getSandboxPath(appContext))
233273
cropOption.isCropDragSmoothToCenter(true)
234274
cropOption.isForbidSkipMultipleCrop(true)
@@ -237,21 +277,48 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
237277
cropOption.setStatusBarColor(Color.WHITE)
238278
cropOption.isDarkStatusBarBlack(true)
239279
cropOption.isDragCropImages(true)
240-
cropOption.setFreeStyleCropEnabled(true)
280+
cropOption.setFreeStyleCropEnabled(config?.freeStyle ?: true)
241281
cropOption.setSkipCropMimeType(*getNotSupportCrop())
242282

243-
cropOption.apply {
244-
setAspectRatioOptions(
245-
1,
246-
AspectRatio(null, 1f, 2f),
247-
AspectRatio(null, 3f, 4f),
248-
AspectRatio(null, 5f, 3f),
283+
284+
val ratioCount = config?.ratio?.size ?: 0
285+
286+
if (config?.defaultRatio != null || ratioCount > 0) {
287+
288+
var ratioList = arrayOf(AspectRatio("Original", 0f, 0f))
289+
290+
if (ratioCount > 0) {
291+
config?.ratio?.take(4)?.toTypedArray()?.forEach { item ->
292+
ratioList += AspectRatio(
293+
item.title, item.width.toFloat(), item.height.toFloat()
294+
)
295+
}
296+
}
297+
298+
// Add default Aspects
299+
ratioList += arrayOf(
300+
AspectRatio(null, 1f, 1f),
249301
AspectRatio(null, 16f, 9f),
250-
AspectRatio(null, 1f, 1f)
302+
AspectRatio(null, 4f, 3f),
303+
AspectRatio(null, 3f, 2f)
251304
)
252-
}
253305

254-
cropOption.setRat
306+
config?.defaultRatio?.let {
307+
val defaultRatio = AspectRatio(it.title, it.width.toFloat(), it.height.toFloat())
308+
ratioList = arrayOf(defaultRatio) + ratioList
309+
310+
}
311+
312+
cropOption.apply {
313+
314+
setAspectRatioOptions(
315+
0,
316+
*ratioList.take(5).toTypedArray()
317+
)
318+
319+
}
320+
321+
}
255322
}
256323

257324

Diff for: example/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"@react-native/typescript-config": "0.75.2",
3232
"@types/react": "~18.2.45",
3333
"react-native-builder-bob": "^0.30.0",
34-
"react-native-nitro-modules": "0.18.1",
34+
"react-native-nitro-modules": "0.18.2",
3535
"typescript": "^5.1.3"
3636
},
3737
"private": true

Diff for: example/src/components/SegmentControl.tsx

-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
11
import React from 'react'
2-
import { StyleSheet, View } from 'react-native'
32
import SegmentedControl, {
43
SegmentedControlProps,
54
} from '@react-native-segmented-control/segmented-control'
65

76
export function SegmentControl({ ...props }: SegmentedControlProps) {
87
return <SegmentedControl {...props} />
98
}
10-
11-
const style = StyleSheet.create({
12-
segment: {
13-
marginHorizontal: 16,
14-
},
15-
})

Diff for: example/src/index.tsx

+21-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
Result,
2121
defaultOptions,
2222
Config,
23-
openCrop,
23+
openCropper,
2424
} from '@baronha/react-native-multiple-image-picker'
2525
import { useImmer } from 'use-immer'
2626
import { StatusBar } from 'expo-status-bar'
@@ -82,7 +82,13 @@ export default function App() {
8282
const response = await openPicker({
8383
...options,
8484
selectedAssets: images,
85-
crop: {},
85+
crop: {
86+
ratio: [
87+
{ title: 'Instagram', width: 1, height: 1 },
88+
{ title: 'Twitter', width: 16, height: 9 },
89+
{ title: 'Facebook', width: 12, height: 11 },
90+
],
91+
},
8692
})
8793

8894
setImages(Array.isArray(response) ? response : [response])
@@ -95,11 +101,21 @@ export default function App() {
95101
const onCrop = async () => {
96102
try {
97103
console.log('images: ', images)
98-
const response = await openCrop(images[0].path, {
99-
circle: true,
104+
const response = await openCropper(images[0].path, {
105+
ratio: [
106+
{ title: 'Instagram', width: 1, height: 1 },
107+
{ title: 'Twitter', width: 16, height: 9 },
108+
{ title: 'Facebook', width: 12, height: 11 },
109+
],
100110
})
101111

102-
console.log('response: ', response)
112+
setImages((prev) => {
113+
const data = [...prev]
114+
data[0].path = response.path
115+
data[0].width = response.width
116+
data[0].height = response.height
117+
return data
118+
})
103119
layoutEffect()
104120
} catch (e) {
105121
console.log('e: ', e)

Diff for: example/yarn.lock

+4-4
Original file line numberDiff line numberDiff line change
@@ -6192,10 +6192,10 @@ react-native-builder-bob@^0.30.0:
61926192
which "^2.0.2"
61936193
yargs "^17.5.1"
61946194

6195-
6196-
version "0.18.1"
6197-
resolved "https://registry.npmjs.org/react-native-nitro-modules/-/react-native-nitro-modules-0.18.1.tgz"
6198-
integrity sha512-F1PA92N8Qv/0I3gKnUFU/eP2C16TSSWwuWuUJnVXX4pCrZztP6BHSvRAZj9WpwxytoKICjwgeVk8K//kvZDZAg==
6195+
6196+
version "0.18.2"
6197+
resolved "https://registry.yarnpkg.com/react-native-nitro-modules/-/react-native-nitro-modules-0.18.2.tgz#f1172f90ecaded0e4b3306f6e7be4b54a24b310e"
6198+
integrity sha512-eHsq1cRfm/Bz1Nq7KctTqxAqhzVSNo0WGX281xARZh+vOq8633Qxn1NHRZ5/Rno2Bla6HOXlUW6RoW0wKM/7kg==
61996199

62006200
62016201
version "0.75.0"

Diff for: ios/HybridMultipleImagePicker+Config.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ extension HybridMultipleImagePicker {
115115
config.editorOptions = [.photo, .gifPhoto, .livePhoto]
116116

117117
if let crop = options.crop {
118-
let editor = setCropConfig(circle: crop.circle, ratio: crop.ratio)
118+
let editor = setCropConfig(crop)
119119

120120
config.editor = editor
121121

Diff for: ios/HybridMultipleImagePicker+Crop.swift

+26-15
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,11 @@ extension HybridMultipleImagePicker {
2929
asset = .init(type: .photoAsset(.init(localIdentifier: image)))
3030
}
3131

32-
let editConfig = setCropConfig(circle: config.circle, ratio: config.ratio)
33-
//
34-
// switch Int(config.presentation.rawValue) {
35-
// case 1:
36-
// editConfig.modalPresentationStyle = .formSheet
37-
// default:
38-
// editConfig.modalPresentationStyle = .fullScreen
39-
// }
40-
//
41-
// editConfig.languageType = setLocale(language: options.language)
32+
let cropOption = PickerCropConfig(circle: config.circle, ratio: config.ratio, defaultRatio: config.defaultRatio, freeStyle: config.freeStyle)
33+
34+
var editConfig = setCropConfig(cropOption)
35+
36+
editConfig.languageType = setLocale(language: config.language)
4237

4338
DispatchQueue.main.async {
4439
Photo.edit(asset: asset, config: editConfig) { result, _ in
@@ -52,16 +47,26 @@ extension HybridMultipleImagePicker {
5247
}
5348
}
5449

55-
func setCropConfig(circle: Bool? = false, ratio: [CropRatio]) -> EditorConfiguration {
50+
func setCropConfig(_ cropConfig: PickerCropConfig) -> EditorConfiguration {
5651
var config = EditorConfiguration()
5752

53+
if let defaultRatio = cropConfig.defaultRatio {
54+
config.cropSize.aspectRatio = .init(width: defaultRatio.width, height: defaultRatio.height)
55+
}
56+
5857
config.photo.defaultSelectedToolOption = .cropSize
5958

6059
config.isFixedCropSizeState = true
6160

61+
config.cropSize.defaultSeletedIndex = 0
62+
63+
let freeStyle = cropConfig.freeStyle ?? true
64+
65+
config.cropSize.isFixedRatio = !freeStyle
66+
6267
config.isWhetherFinishButtonDisabledInUneditedState = true
6368

64-
config.cropSize.isRoundCrop = circle ?? false
69+
config.cropSize.isRoundCrop = cropConfig.circle ?? false
6570

6671
config.cropSize.isResetToOriginal = true
6772

@@ -72,19 +77,25 @@ extension HybridMultipleImagePicker {
7277
if config.cropSize.isRoundCrop {
7378
config.cropSize.aspectRatios = []
7479
} else {
75-
var aspectRatios: [EditorRatioToolConfig] = config.cropSize.aspectRatios
80+
var aspectRatios: [EditorRatioToolConfig] = PickerConfiguration.default.editor.cropSize.aspectRatios
7681

82+
let ratio = cropConfig.ratio
7783
// custom ratio
7884
if ratio.count > 0 {
7985
ratio.forEach { ratio in
8086
let width = Int(ratio.width)
8187
let height = Int(ratio.height)
8288

83-
aspectRatios.append(.init(title: .custom(ratio.title ?? "\(width)/\(height)"), ratio: .init(width: width, height: height)))
89+
aspectRatios.insert(.init(title: .custom(ratio.title ?? "\(width)/\(height)"), ratio: .init(width: width, height: height)), at: 3)
8490
}
8591
}
8692

87-
config.cropSize.aspectRatios = aspectRatios
93+
config.cropSize.aspectRatios = freeStyle ? aspectRatios : aspectRatios.filter {
94+
// check freeStyle crop
95+
if $0.ratio == .zero { return false }
96+
97+
return true
98+
}
8899
}
89100

90101
return config

Diff for: ios/TopViewController.swift

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
//
2-
// UIViewController+TopViewController.swift
2+
// TopViewController.swift
33
// Pods
44
//
55
// Created by BAO HA on 11/12/24.
66
//
77

88
import UIKit
99

10-
extension UIViewController {
11-
func topViewController() -> UIViewController? {
12-
var controller = UIApplication.shared.keyWindow?.rootViewController
13-
while let presentedViewController = controller?.presentedViewController {
14-
controller = presentedViewController
15-
}
16-
17-
return controller
10+
func getTopViewController() -> UIViewController? {
11+
var controller = UIApplication.shared.keyWindow?.rootViewController
12+
while let presentedViewController = controller?.presentedViewController {
13+
controller = presentedViewController
1814
}
15+
16+
return controller
1917
}

Diff for: nitro.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"$schema": "https://nitro.margelo.com/nitro.schema.json",
23
"cxxNamespace": ["multipleimagepicker"],
34
"ios": {
45
"iosModuleName": "MultipleImagePicker"

0 commit comments

Comments
 (0)