Skip to content

Commit 52384bc

Browse files
authored
Merge pull request #190 from baronha/feature/camera
📸 Camera
2 parents ce61b7c + f164741 commit 52384bc

File tree

67 files changed

+2238
-270
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2238
-270
lines changed

MultipleImagePicker.podspec

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ Pod::Spec.new do |s|
2828

2929

3030
s.dependency "HXPhotoPicker/Picker", "4.2.4"
31-
s.dependency "HXPhotoPicker/Editor/Lite", "4.2.4"
31+
s.dependency "HXPhotoPicker/Camera/Lite", "4.2.4"
32+
s.dependency "HXPhotoPicker/Editor", "4.2.4"
3233

3334
s.pod_target_xcconfig = {
3435
# C++ compiler flags, mainly for folly.

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ React Native Multiple Image Picker **(RNMIP)** enables application to pick image
2727
| 📺 | Display video duration. |
2828
| 🎆 | Preview image/video. |
2929
| ⛅️ | Support iCloud Photo Library. |
30-
| 🔪 | Crop single/multiple image (new) ✨ |
30+
| 🍕 | Crop single/multiple image (new) ✨ |
3131
| 🌪 | Scrolling performance. ☕️ |
3232

3333
## Installation

android/src/main/java/com/margelo/nitro/multipleimagepicker/CameraEngine.kt

+17-6
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,37 @@ package com.margelo.nitro.multipleimagepicker
33
import android.content.Context
44
import androidx.fragment.app.Fragment
55
import com.bumptech.glide.Glide
6+
import com.facebook.react.bridge.ColorPropConverter
67
import com.luck.lib.camerax.SimpleCameraX
78
import com.luck.picture.lib.interfaces.OnCameraInterceptListener
89
import java.io.File
910

10-
class CameraEngine(private val appContext: Context, val config: PickerCameraConfig) : OnCameraInterceptListener {
11+
class CameraEngine(
12+
private val appContext: Context,
13+
val config: NitroCameraConfig,
14+
) :
15+
OnCameraInterceptListener {
1116
override fun openCamera(fragment: Fragment, cameraMode: Int, requestCode: Int) {
1217
val camera = SimpleCameraX.of()
18+
19+
camera.setImageEngine { context, url, imageView ->
20+
Glide.with(context).load(url).into(imageView)
21+
}
22+
1323
camera.isAutoRotation(true)
1424
camera.setCameraMode(cameraMode)
1525
camera.isDisplayRecordChangeTime(true)
1626
camera.isManualFocusCameraPreview(true)
1727
camera.isZoomCameraPreview(true)
18-
camera.setOutputPathDir(getSandboxCameraOutputPath())
1928
camera.setRecordVideoMaxSecond(config.videoMaximumDuration?.toInt() ?: 60)
29+
camera.setCameraAroundState(config.cameraDevice == CameraDevice.FRONT)
30+
camera.setOutputPathDir(getSandboxCameraOutputPath())
2031

21-
// camera.setPermissionDeniedListener(getSimpleXPermissionDeniedListener())
22-
// camera.setPermissionDescriptionListener(getSimpleXPermissionDescriptionListener())
23-
camera.setImageEngine { context, url, imageView ->
24-
Glide.with(context).load(url).into(imageView)
32+
config.color?.let {
33+
val primaryColor = ColorPropConverter.getColor(it, appContext)
34+
camera.setCaptureLoadingColor(primaryColor)
2535
}
36+
2637
camera.start(fragment.requireActivity(), fragment, requestCode)
2738
}
2839

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

+8
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,12 @@ class MultipleImagePicker : HybridMultipleImagePickerSpec() {
3434
pickerModule.openPreview(media, index.toInt(), config)
3535
}
3636

37+
override fun openCamera(
38+
config: NitroCameraConfig,
39+
resolved: (result: CameraResult) -> Unit,
40+
rejected: (reject: Double) -> Unit
41+
) {
42+
pickerModule.openCamera(config, resolved, rejected)
43+
}
44+
3745
}

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

+91-15
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
7373
setStyle() // set style for UI
7474
handleSelectedAssets(config)
7575

76-
val chooseMode = when (config.mediaType) {
77-
MediaType.VIDEO -> SelectMimeType.ofVideo()
78-
MediaType.IMAGE -> SelectMimeType.ofImage()
79-
else -> SelectMimeType.ofAll()
80-
}
76+
val chooseMode = getChooseMode(config.mediaType)
8177

8278
val maxSelect = config.maxSelect?.toInt() ?: 20
8379
val maxVideo = config.maxVideo?.toInt() ?: 20
@@ -96,7 +92,6 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
9692
.setImageEngine(imageEngine)
9793
.setSelectedData(dataList)
9894
.setSelectorUIStyle(style)
99-
10095
.apply {
10196
if (isCrop) {
10297
setCropOption(config.crop)
@@ -116,13 +111,24 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
116111
setFilterMaxFileSize(it)
117112
}
118113

119-
120114
isDisplayCamera(config.camera != null)
121115

122116
config.camera?.let {
123-
setCameraInterceptListener(CameraEngine(appContext, it))
117+
val cameraConfig = NitroCameraConfig(
118+
mediaType = MediaType.ALL,
119+
presentation = Presentation.FULLSCREENMODAL,
120+
language = Language.SYSTEM,
121+
crop = null,
122+
isSaveSystemAlbum = false,
123+
color = config.primaryColor,
124+
cameraDevice = it.cameraDevice,
125+
videoMaximumDuration = it.videoMaximumDuration
126+
)
127+
128+
setCameraInterceptListener(CameraEngine(appContext, cameraConfig))
124129
}
125130
}
131+
.setVideoThumbnailListener(VideoThumbnailEngine(getVideoThumbnailDir()))
126132
.setImageSpanCount(config.numberOfColumn?.toInt() ?: 3)
127133
.setMaxSelectNum(maxSelect)
128134
.isDirectReturnSingle(true)
@@ -301,7 +307,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
301307
.setLanguage(getLanguage(config.language))
302308
.setSelectorUIStyle(previewStyle)
303309
.isPreviewFullScreenMode(true)
304-
.isAutoVideoPlay(true)
310+
.isAutoVideoPlay(config.videoAutoPlay == true)
305311
.setVideoPlayerEngine(ExoPlayerEngine())
306312
.isVideoPauseResumePlay(true)
307313
.setCustomLoadingListener(getCustomLoadingListener())
@@ -312,6 +318,70 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
312318
return OnCustomLoadingListener { context -> LoadingDialog(context) }
313319
}
314320

321+
@ReactMethod
322+
fun openCamera(
323+
config: NitroCameraConfig,
324+
resolved: (result: CameraResult) -> Unit,
325+
rejected: (reject: Double) -> Unit
326+
) {
327+
val activity = currentActivity
328+
val chooseMode = getChooseMode(config.mediaType)
329+
330+
PictureSelector
331+
.create(activity)
332+
.openCamera(chooseMode)
333+
.setLanguage(getLanguage(config.language))
334+
.setCameraInterceptListener(CameraEngine(appContext, config))
335+
.isQuickCapture(true)
336+
.isOriginalControl(true)
337+
.setVideoThumbnailListener(VideoThumbnailEngine(getVideoThumbnailDir()))
338+
.apply {
339+
if (config.crop != null) {
340+
setCropEngine(CropEngine(cropOption))
341+
}
342+
}
343+
.forResultActivity(object : OnResultCallbackListener<LocalMedia?> {
344+
override fun onResult(results: java.util.ArrayList<LocalMedia?>?) {
345+
results?.first()?.let {
346+
val result = getResult(it)
347+
348+
resolved(
349+
CameraResult(
350+
path = result.path,
351+
type = result.type,
352+
width = result.width,
353+
height = result.height,
354+
duration = result.duration,
355+
thumbnail = result.thumbnail,
356+
fileName = result.fileName
357+
)
358+
)
359+
}
360+
}
361+
362+
override fun onCancel() {
363+
// rejected(0.0)
364+
}
365+
})
366+
}
367+
368+
private fun getChooseMode(mediaType: MediaType): Int {
369+
return when (mediaType) {
370+
MediaType.VIDEO -> SelectMimeType.ofVideo()
371+
MediaType.IMAGE -> SelectMimeType.ofImage()
372+
else -> SelectMimeType.ofAll()
373+
}
374+
}
375+
376+
private fun getVideoThumbnailDir(): String {
377+
val externalFilesDir: File? = appContext.getExternalFilesDir("")
378+
val customFile = File(externalFilesDir?.absolutePath, "Thumbnail")
379+
if (!customFile.exists()) {
380+
customFile.mkdirs()
381+
}
382+
return customFile.absolutePath + File.separator
383+
}
384+
315385

316386
private fun getLanguage(language: Language): Int {
317387
return when (language) {
@@ -511,19 +581,23 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
511581
if (item.mimeType.startsWith("video/")) ResultType.VIDEO else ResultType.IMAGE
512582

513583
var path = item.path
514-
515584
var width: Double = item.width.toDouble()
516585
var height: Double = item.height.toDouble()
517586

587+
val thumbnail = item.videoThumbnailPath?.let {
588+
if (!it.startsWith("file://")) "file://$it" else it
589+
}
590+
518591
if (item.isCut) {
519592
path = "file://${item.cutPath}"
520593
width = item.cropImageWidth.toDouble()
521594
height = item.cropImageHeight.toDouble()
522595
}
523596

597+
if (!path.startsWith("file://") && !path.startsWith("content://") && type == ResultType.IMAGE)
598+
path = "file://$path"
599+
524600
val media = Result(
525-
path,
526-
fileName = item.fileName,
527601
localIdentifier = item.id.toString(),
528602
width,
529603
height,
@@ -533,10 +607,12 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
533607
realPath = item.realPath,
534608
parentFolderName = item.parentFolderName,
535609
creationDate = item.dateAddedTime.toDouble(),
610+
crop = item.isCut,
611+
path,
536612
type,
537-
duration = item.duration.toDouble(),
538-
thumbnail = item.videoThumbnailPath,
539-
crop = item.isCut
613+
fileName = item.fileName,
614+
thumbnail = thumbnail,
615+
duration = item.duration.toDouble()
540616
)
541617

542618
return media
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.margelo.nitro.multipleimagepicker
2+
3+
import android.content.Context
4+
import android.graphics.Bitmap
5+
import android.graphics.drawable.Drawable
6+
import com.bumptech.glide.Glide
7+
import com.bumptech.glide.request.target.CustomTarget
8+
import com.bumptech.glide.request.transition.Transition
9+
import com.luck.picture.lib.interfaces.OnKeyValueResultCallbackListener
10+
import com.luck.picture.lib.interfaces.OnVideoThumbnailEventListener
11+
import com.luck.picture.lib.utils.PictureFileUtils
12+
import java.io.ByteArrayOutputStream
13+
import java.io.File
14+
import java.io.FileOutputStream
15+
import java.io.IOException
16+
17+
18+
class VideoThumbnailEngine(private val targetPath: String) : OnVideoThumbnailEventListener {
19+
override fun onVideoThumbnail(
20+
context: Context, videoPath: String, call: OnKeyValueResultCallbackListener
21+
) {
22+
Glide.with(context).asBitmap().sizeMultiplier(0.6f).load(videoPath)
23+
.into(object : CustomTarget<Bitmap?>() {
24+
override fun onResourceReady(
25+
resource: Bitmap, transition: Transition<in Bitmap?>?
26+
) {
27+
val stream = ByteArrayOutputStream()
28+
resource.compress(Bitmap.CompressFormat.JPEG, 60, stream)
29+
var fos: FileOutputStream? = null
30+
var result: String? = null
31+
try {
32+
val targetFile =
33+
File(targetPath, "thumbnails_" + System.currentTimeMillis() + ".jpg")
34+
fos = FileOutputStream(targetFile)
35+
fos.write(stream.toByteArray())
36+
fos.flush()
37+
result = targetFile.absolutePath
38+
} catch (e: IOException) {
39+
e.printStackTrace()
40+
} finally {
41+
PictureFileUtils.close(fos)
42+
PictureFileUtils.close(stream)
43+
}
44+
call.onCallback(videoPath, result)
45+
}
46+
47+
override fun onLoadCleared(placeholder: Drawable?) {
48+
call.onCallback(videoPath, "")
49+
}
50+
})
51+
}
52+
}

0 commit comments

Comments
 (0)