Skip to content

Commit a996d84

Browse files
committedDec 12, 2024
✨ feat: done handle preview on iOS
1 parent 5297307 commit a996d84

31 files changed

+867
-63
lines changed
 

‎MultipleImagePicker.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Pod::Spec.new do |s|
2727
}
2828

2929

30-
s.dependency "HXPhotoPicker/Picker/Lite", "4.2.3"
30+
s.dependency "HXPhotoPicker/Picker", "4.2.3"
3131
s.dependency "HXPhotoPicker/Editor/Lite", "4.2.3"
3232

3333
s.pod_target_xcconfig = {

‎android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePicker.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ class MultipleImagePicker : HybridMultipleImagePickerSpec() {
2323
resolved: (result: CropResult) -> Unit,
2424
rejected: (reject: Double) -> Unit
2525
) {
26-
2726
pickerModule.openCrop(image, config, resolved, rejected)
2827
}
2928

29+
override fun openPreview(media: Array<MediaPreview>, config: NitroPreviewConfig) {
30+
pickerModule.openPreview(media, config)
31+
}
3032

3133
}

‎android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePickerImp.kt

+39-31
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.luck.picture.lib.basic.PictureSelector
1919
import com.luck.picture.lib.config.PictureMimeType
2020
import com.luck.picture.lib.config.SelectMimeType
2121
import com.luck.picture.lib.config.SelectModeConfig
22+
import com.luck.picture.lib.engine.ImageEngine
2223
import com.luck.picture.lib.engine.PictureSelectorEngine
2324
import com.luck.picture.lib.entity.LocalMedia
2425
import com.luck.picture.lib.interfaces.OnMediaEditInterceptListener
@@ -92,11 +93,13 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
9293
val isMultiple = config.selectMode == SelectMode.MULTIPLE
9394
val selectMode = if (isMultiple) SelectModeConfig.MULTIPLE else SelectModeConfig.SINGLE
9495

95-
9696
val isCrop = config.crop != null
9797

98-
PictureSelector.create(activity).openGallery(chooseMode).setImageEngine(imageEngine)
99-
.setSelectedData(dataList).setSelectorUIStyle(style).apply {
98+
PictureSelector.create(activity)
99+
.openGallery(chooseMode)
100+
.setImageEngine(imageEngine)
101+
.setSelectedData(dataList)
102+
.setSelectorUIStyle(style).apply {
100103
if (isCrop) {
101104
setCropOption(config.crop)
102105
// Disabled force crop engine for multiple
@@ -123,18 +126,27 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
123126
if (videoQuality != null && videoQuality != 1.0) {
124127
setVideoQuality(if (videoQuality > 0.5) 1 else 0)
125128
}
126-
}.setImageSpanCount(config.numberOfColumn?.toInt() ?: 3).setMaxSelectNum(maxSelect)
127-
.isDirectReturnSingle(true).isSelectZoomAnim(true).isPageStrategy(true, 50)
129+
}.setImageSpanCount(config.numberOfColumn?.toInt() ?: 3)
130+
.setMaxSelectNum(maxSelect)
131+
.isDirectReturnSingle(true)
132+
.isSelectZoomAnim(true)
133+
.isPageStrategy(true, 50)
128134
.isWithSelectVideoImage(true)
129135
.setMaxVideoSelectNum(if (maxVideo != 20) maxVideo else maxSelect)
130-
.isMaxSelectEnabledMask(true).isAutoVideoPlay(true)
131-
.isFastSlidingSelect(allowSwipeToSelect).isPageSyncAlbumCount(true)
136+
.isMaxSelectEnabledMask(true)
137+
.isAutoVideoPlay(true)
138+
.isFastSlidingSelect(allowSwipeToSelect)
139+
.isPageSyncAlbumCount(true)
132140
// isPreview
133-
.isPreviewImage(isPreview).isPreviewVideo(isPreview)
141+
.isPreviewImage(isPreview)
142+
.isPreviewVideo(isPreview)
134143
//
135-
.isDisplayCamera(config.allowedCamera ?: true).isDisplayTimeAxis(true)
136-
.setSelectionMode(selectMode).isOriginalControl(config.isHiddenOriginalButton == false)
137-
.setLanguage(getLanguage()).isPreviewFullScreenMode(true)
144+
.isDisplayCamera(config.allowedCamera ?: true)
145+
.isDisplayTimeAxis(true)
146+
.setSelectionMode(selectMode)
147+
.isOriginalControl(config.isHiddenOriginalButton == false)
148+
.setLanguage(getLanguage(config.language))
149+
.isPreviewFullScreenMode(true)
138150
.forResult(object : OnResultCallbackListener<LocalMedia?> {
139151
override fun onResult(localMedia: ArrayList<LocalMedia?>?) {
140152
var data: Array<Result> = arrayOf()
@@ -168,18 +180,6 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
168180
resolved: (result: CropResult) -> Unit,
169181
rejected: (reject: Double) -> Unit
170182
) {
171-
172-
173-
fun isImage(uri: Uri, contentResolver: ContentResolver): Boolean {
174-
val mimeType: String? = contentResolver.getType(uri)
175-
return mimeType?.startsWith("image/") == true
176-
}
177-
178-
val uri = Uri.parse(image)
179-
val isImageFile = isImage(uri, appContext.contentResolver)
180-
181-
if (!isImageFile) return rejected(0.0)
182-
183183
cropOption = Options()
184184

185185
setCropOption(
@@ -211,17 +211,12 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
211211
Uri.fromFile(file)
212212
}
213213

214-
215-
else -> {
216-
Uri.parse(image)
217-
}
214+
else -> Uri.parse(image)
218215
}
219216

220-
221217
val destinationUri = Uri.fromFile(
222218
File(getSandboxPath(appContext), DateUtils.getCreateFileName("CROP_") + ".jpeg")
223219
)
224-
225220
val uCrop = UCrop.of<Any>(uri, destinationUri).withOptions(cropOption)
226221

227222
// set engine
@@ -267,8 +262,21 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
267262
}
268263
}
269264

270-
private fun getLanguage(): Int {
271-
return when (config.language) {
265+
@ReactMethod
266+
fun openPreview(media: Array<MediaPreview>, config: NitroPreviewConfig) {
267+
268+
val imageEngine = GlideEngine.createGlideEngine()
269+
270+
PictureSelector
271+
.create(currentActivity)
272+
.openPreview()
273+
.setImageEngine(imageEngine)
274+
.setLanguage(getLanguage(config.language))
275+
.
276+
}
277+
278+
private fun getLanguage(language: Language): Int {
279+
return when (language) {
272280
Language.VI -> LanguageConfig.VIETNAM // -> 🇻🇳 My country. Yeahhh
273281
Language.EN -> LanguageConfig.ENGLISH
274282
Language.ZH_HANS -> LanguageConfig.CHINESE

‎example/src/index.tsx

+24-10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
defaultOptions,
2222
Config,
2323
openCropper,
24+
openPreview,
25+
MediaPreview,
2426
} from '@baronha/react-native-multiple-image-picker'
2527
import { useImmer } from 'use-immer'
2628
import { StatusBar } from 'expo-status-bar'
@@ -73,8 +75,17 @@ export default function App() {
7375
})
7476
}
7577

76-
const onPressImage = () => {
77-
//
78+
const onPressImage = (_: Result, index: number) => {
79+
openPreview(
80+
[
81+
{
82+
path: 'http://tsnrhapp.oss-cn-hangzhou.aliyuncs.com/chartle/IMG_3385.MP4',
83+
type: 'video',
84+
} as MediaPreview,
85+
...images,
86+
],
87+
{ index, backgroundColor: 'red' }
88+
)
7889
}
7990

8091
const onPicker = async () => {
@@ -101,13 +112,16 @@ export default function App() {
101112
const onCrop = async () => {
102113
try {
103114
console.log('images: ', images)
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-
],
110-
})
115+
const response = await openCropper(
116+
'https://images.unsplash.com/photo-1610562275255-03b7fa0d4655?q=80&w=2861&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
117+
{
118+
ratio: [
119+
{ title: 'Instagram', width: 1, height: 1 },
120+
{ title: 'Twitter', width: 16, height: 9 },
121+
{ title: 'Facebook', width: 12, height: 11 },
122+
],
123+
}
124+
)
111125

112126
setImages((prev) => {
113127
const data = [...prev]
@@ -123,7 +137,7 @@ export default function App() {
123137
}
124138

125139
const onRemovePhoto = (_: Result, index: number) => {
126-
const data = [...images].filter((_, idx) => idx !== index)
140+
const data = [...images].filter((__, idx) => idx !== index)
127141
setImages(data)
128142
}
129143

‎ios/HybridMultipleImagePicker+Crop.swift

-18
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,11 @@
66
//
77

88
import HXPhotoPicker
9-
import MobileCoreServices
10-
11-
// class CropConfig: PickerCropConfig {
12-
// //
13-
// }
149

1510
extension HybridMultipleImagePicker {
1611
func openCrop(image: String, config: NitroCropConfig, resolved: @escaping ((CropResult) -> Void), rejected: @escaping ((Double) -> Void)) throws {
1712
let asset: EditorAsset
1813

19-
if !isImage(image) { return rejected(0) }
20-
2114
if image.hasPrefix("http://") || image.hasPrefix("https://") || image.hasPrefix("file://") {
2215
guard let url = URL(string: image),
2316
let data = try? Data(contentsOf: url)
@@ -104,14 +97,3 @@ extension HybridMultipleImagePicker {
10497
return config
10598
}
10699
}
107-
108-
private func isImage(_ urlString: String) -> Bool {
109-
guard let url = URL(string: urlString),
110-
let pathExtension = url.pathExtension as CFString?,
111-
let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, nil)?.takeRetainedValue()
112-
else {
113-
return false
114-
}
115-
116-
return UTTypeConformsTo(uti, kUTTypeImage)
117-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// HybridMultipleImagePicker+Preview.swift
3+
// Pods
4+
//
5+
// Created by BAO HA on 11/12/24.
6+
//
7+
8+
import HXPhotoPicker
9+
import Kingfisher
10+
11+
extension HybridMultipleImagePicker {
12+
func openPreview(media: [MediaPreview], config: NitroPreviewConfig) throws {
13+
var previewConfig = HXPhotoPicker.PhotoBrowser.Configuration()
14+
previewConfig.showDelete = false
15+
16+
let index = config.index
17+
var assets: [PhotoAsset] = []
18+
19+
previewConfig.tintColor = .white
20+
previewConfig.videoPlayType = .auto
21+
previewConfig.livePhotoPlayType = .auto
22+
23+
previewConfig.languageType = setLocale(language: config.language)
24+
25+
media.forEach { mediaItem in
26+
27+
var asset: PhotoAsset?
28+
29+
if let localIdentifier = mediaItem.localIdentifier {
30+
asset = .init(localIdentifier: localIdentifier)
31+
32+
// auto play gif
33+
if let filePath = mediaItem.path, asset?.isGifAsset == true,
34+
let url = URL(string: filePath)
35+
{
36+
asset = .init(.init(imageURL: url))
37+
}
38+
39+
} else if let path = mediaItem.path, let url = URL(string: path) {
40+
let thumbnail = URL(string: mediaItem.thumbnail ?? "") ?? url
41+
42+
if mediaItem.type == .image {
43+
// network asset
44+
if path.hasPrefix("https://") || path.hasPrefix("http://") {
45+
asset = PhotoAsset(NetworkImageAsset(
46+
thumbnailURL: thumbnail,
47+
originalURL: url,
48+
thumbnailLoadMode: .alwaysThumbnail,
49+
originalLoadMode: .alwaysThumbnail
50+
))
51+
52+
} else {
53+
asset = .init(.init(imageURL: url))
54+
}
55+
} else {
56+
asset = .init(networkVideoAsset: .init(videoURL: url, coverImageURL: thumbnail))
57+
}
58+
}
59+
60+
if let asset {
61+
assets.append(asset)
62+
}
63+
}
64+
65+
DispatchQueue.main.async {
66+
HXPhotoPicker.PhotoBrowser.show(
67+
assets,
68+
pageIndex: Int(index),
69+
config: previewConfig
70+
)
71+
}
72+
}
73+
}

‎ios/Utils.swift

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// Utils.swift
3+
// Pods
4+
//
5+
// Created by BAO HA on 11/12/24.
6+
//
7+
8+
import MobileCoreServices
9+
10+
func isImage(_ urlString: String) -> Bool {
11+
guard let url = URL(string: urlString),
12+
let pathExtension = url.pathExtension as CFString?,
13+
let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, nil)?
14+
.takeRetainedValue()
15+
else {
16+
return false
17+
}
18+
19+
return UTTypeConformsTo(uti, kUTTypeImage)
20+
}

‎nitrogen/generated/android/c++/JHybridMultipleImagePickerSpec.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ namespace margelo::nitro::multipleimagepicker { enum class Presentation; }
3535
namespace margelo::nitro::multipleimagepicker { struct NitroCropConfig; }
3636
// Forward declaration of `CropResult` to properly resolve imports.
3737
namespace margelo::nitro::multipleimagepicker { struct CropResult; }
38+
// Forward declaration of `MediaPreview` to properly resolve imports.
39+
namespace margelo::nitro::multipleimagepicker { struct MediaPreview; }
40+
// Forward declaration of `NitroPreviewConfig` to properly resolve imports.
41+
namespace margelo::nitro::multipleimagepicker { struct NitroPreviewConfig; }
3842

3943
#include "NitroConfig.hpp"
4044
#include "JNitroConfig.hpp"
@@ -71,6 +75,10 @@ namespace margelo::nitro::multipleimagepicker { struct CropResult; }
7175
#include "CropResult.hpp"
7276
#include "JFunc_void_CropResult.hpp"
7377
#include "JCropResult.hpp"
78+
#include "MediaPreview.hpp"
79+
#include "JMediaPreview.hpp"
80+
#include "NitroPreviewConfig.hpp"
81+
#include "JNitroPreviewConfig.hpp"
7482

7583
namespace margelo::nitro::multipleimagepicker {
7684

@@ -101,5 +109,17 @@ namespace margelo::nitro::multipleimagepicker {
101109
static const auto method = _javaPart->getClass()->getMethod<void(jni::alias_ref<jni::JString> /* image */, jni::alias_ref<JNitroCropConfig> /* config */, jni::alias_ref<JFunc_void_CropResult::javaobject> /* resolved */, jni::alias_ref<JFunc_void_double::javaobject> /* rejected */)>("openCrop");
102110
method(_javaPart, jni::make_jstring(image), JNitroCropConfig::fromCpp(config), JFunc_void_CropResult::fromCpp(resolved), JFunc_void_double::fromCpp(rejected));
103111
}
112+
void JHybridMultipleImagePickerSpec::openPreview(const std::vector<MediaPreview>& media, const NitroPreviewConfig& config) {
113+
static const auto method = _javaPart->getClass()->getMethod<void(jni::alias_ref<jni::JArrayClass<JMediaPreview>> /* media */, jni::alias_ref<JNitroPreviewConfig> /* config */)>("openPreview");
114+
method(_javaPart, [&]() {
115+
size_t __size = media.size();
116+
jni::local_ref<jni::JArrayClass<JMediaPreview>> __array = jni::JArrayClass<JMediaPreview>::newArray(__size);
117+
for (size_t __i = 0; __i < __size; __i++) {
118+
const auto& __element = media[__i];
119+
__array->setElement(__i, *JMediaPreview::fromCpp(__element));
120+
}
121+
return __array;
122+
}(), JNitroPreviewConfig::fromCpp(config));
123+
}
104124

105125
} // namespace margelo::nitro::multipleimagepicker

‎nitrogen/generated/android/c++/JHybridMultipleImagePickerSpec.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ namespace margelo::nitro::multipleimagepicker {
5353
// Methods
5454
void openPicker(const NitroConfig& config, const std::function<void(const std::vector<Result>& /* result */)>& resolved, const std::function<void(double /* reject */)>& rejected) override;
5555
void openCrop(const std::string& image, const NitroCropConfig& config, const std::function<void(const CropResult& /* result */)>& resolved, const std::function<void(double /* reject */)>& rejected) override;
56+
void openPreview(const std::vector<MediaPreview>& media, const NitroPreviewConfig& config) override;
5657

5758
private:
5859
friend HybridBase;

0 commit comments

Comments
 (0)
Please sign in to comment.