Skip to content

Commit

Permalink
Merge pull request #190 from baronha/feature/camera
Browse files Browse the repository at this point in the history
📸 Camera
  • Loading branch information
baronha authored Dec 18, 2024
2 parents ce61b7c + f164741 commit 52384bc
Show file tree
Hide file tree
Showing 67 changed files with 2,238 additions and 270 deletions.
3 changes: 2 additions & 1 deletion MultipleImagePicker.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ Pod::Spec.new do |s|


s.dependency "HXPhotoPicker/Picker", "4.2.4"
s.dependency "HXPhotoPicker/Editor/Lite", "4.2.4"
s.dependency "HXPhotoPicker/Camera/Lite", "4.2.4"
s.dependency "HXPhotoPicker/Editor", "4.2.4"

s.pod_target_xcconfig = {
# C++ compiler flags, mainly for folly.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ React Native Multiple Image Picker **(RNMIP)** enables application to pick image
| 📺 | Display video duration. |
| 🎆 | Preview image/video. |
| ⛅️ | Support iCloud Photo Library. |
| 🔪 | Crop single/multiple image (new) ✨ |
| 🍕 | Crop single/multiple image (new) ✨ |
| 🌪 | Scrolling performance. ☕️ |

## Installation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,37 @@ package com.margelo.nitro.multipleimagepicker
import android.content.Context
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.facebook.react.bridge.ColorPropConverter
import com.luck.lib.camerax.SimpleCameraX
import com.luck.picture.lib.interfaces.OnCameraInterceptListener
import java.io.File

class CameraEngine(private val appContext: Context, val config: PickerCameraConfig) : OnCameraInterceptListener {
class CameraEngine(
private val appContext: Context,
val config: NitroCameraConfig,
) :
OnCameraInterceptListener {
override fun openCamera(fragment: Fragment, cameraMode: Int, requestCode: Int) {
val camera = SimpleCameraX.of()

camera.setImageEngine { context, url, imageView ->
Glide.with(context).load(url).into(imageView)
}

camera.isAutoRotation(true)
camera.setCameraMode(cameraMode)
camera.isDisplayRecordChangeTime(true)
camera.isManualFocusCameraPreview(true)
camera.isZoomCameraPreview(true)
camera.setOutputPathDir(getSandboxCameraOutputPath())
camera.setRecordVideoMaxSecond(config.videoMaximumDuration?.toInt() ?: 60)
camera.setCameraAroundState(config.cameraDevice == CameraDevice.FRONT)
camera.setOutputPathDir(getSandboxCameraOutputPath())

// camera.setPermissionDeniedListener(getSimpleXPermissionDeniedListener())
// camera.setPermissionDescriptionListener(getSimpleXPermissionDescriptionListener())
camera.setImageEngine { context, url, imageView ->
Glide.with(context).load(url).into(imageView)
config.color?.let {
val primaryColor = ColorPropConverter.getColor(it, appContext)
camera.setCaptureLoadingColor(primaryColor)
}

camera.start(fragment.requireActivity(), fragment, requestCode)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,12 @@ class MultipleImagePicker : HybridMultipleImagePickerSpec() {
pickerModule.openPreview(media, index.toInt(), config)
}

override fun openCamera(
config: NitroCameraConfig,
resolved: (result: CameraResult) -> Unit,
rejected: (reject: Double) -> Unit
) {
pickerModule.openCamera(config, resolved, rejected)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
setStyle() // set style for UI
handleSelectedAssets(config)

val chooseMode = when (config.mediaType) {
MediaType.VIDEO -> SelectMimeType.ofVideo()
MediaType.IMAGE -> SelectMimeType.ofImage()
else -> SelectMimeType.ofAll()
}
val chooseMode = getChooseMode(config.mediaType)

val maxSelect = config.maxSelect?.toInt() ?: 20
val maxVideo = config.maxVideo?.toInt() ?: 20
Expand All @@ -96,7 +92,6 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
.setImageEngine(imageEngine)
.setSelectedData(dataList)
.setSelectorUIStyle(style)

.apply {
if (isCrop) {
setCropOption(config.crop)
Expand All @@ -116,13 +111,24 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
setFilterMaxFileSize(it)
}


isDisplayCamera(config.camera != null)

config.camera?.let {
setCameraInterceptListener(CameraEngine(appContext, it))
val cameraConfig = NitroCameraConfig(
mediaType = MediaType.ALL,
presentation = Presentation.FULLSCREENMODAL,
language = Language.SYSTEM,
crop = null,
isSaveSystemAlbum = false,
color = config.primaryColor,
cameraDevice = it.cameraDevice,
videoMaximumDuration = it.videoMaximumDuration
)

setCameraInterceptListener(CameraEngine(appContext, cameraConfig))
}
}
.setVideoThumbnailListener(VideoThumbnailEngine(getVideoThumbnailDir()))
.setImageSpanCount(config.numberOfColumn?.toInt() ?: 3)
.setMaxSelectNum(maxSelect)
.isDirectReturnSingle(true)
Expand Down Expand Up @@ -301,7 +307,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
.setLanguage(getLanguage(config.language))
.setSelectorUIStyle(previewStyle)
.isPreviewFullScreenMode(true)
.isAutoVideoPlay(true)
.isAutoVideoPlay(config.videoAutoPlay == true)
.setVideoPlayerEngine(ExoPlayerEngine())
.isVideoPauseResumePlay(true)
.setCustomLoadingListener(getCustomLoadingListener())
Expand All @@ -312,6 +318,70 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
return OnCustomLoadingListener { context -> LoadingDialog(context) }
}

@ReactMethod
fun openCamera(
config: NitroCameraConfig,
resolved: (result: CameraResult) -> Unit,
rejected: (reject: Double) -> Unit
) {
val activity = currentActivity
val chooseMode = getChooseMode(config.mediaType)

PictureSelector
.create(activity)
.openCamera(chooseMode)
.setLanguage(getLanguage(config.language))
.setCameraInterceptListener(CameraEngine(appContext, config))
.isQuickCapture(true)
.isOriginalControl(true)
.setVideoThumbnailListener(VideoThumbnailEngine(getVideoThumbnailDir()))
.apply {
if (config.crop != null) {
setCropEngine(CropEngine(cropOption))
}
}
.forResultActivity(object : OnResultCallbackListener<LocalMedia?> {
override fun onResult(results: java.util.ArrayList<LocalMedia?>?) {
results?.first()?.let {
val result = getResult(it)

resolved(
CameraResult(
path = result.path,
type = result.type,
width = result.width,
height = result.height,
duration = result.duration,
thumbnail = result.thumbnail,
fileName = result.fileName
)
)
}
}

override fun onCancel() {
// rejected(0.0)
}
})
}

private fun getChooseMode(mediaType: MediaType): Int {
return when (mediaType) {
MediaType.VIDEO -> SelectMimeType.ofVideo()
MediaType.IMAGE -> SelectMimeType.ofImage()
else -> SelectMimeType.ofAll()
}
}

private fun getVideoThumbnailDir(): String {
val externalFilesDir: File? = appContext.getExternalFilesDir("")
val customFile = File(externalFilesDir?.absolutePath, "Thumbnail")
if (!customFile.exists()) {
customFile.mkdirs()
}
return customFile.absolutePath + File.separator
}


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

var path = item.path

var width: Double = item.width.toDouble()
var height: Double = item.height.toDouble()

val thumbnail = item.videoThumbnailPath?.let {
if (!it.startsWith("file://")) "file://$it" else it
}

if (item.isCut) {
path = "file://${item.cutPath}"
width = item.cropImageWidth.toDouble()
height = item.cropImageHeight.toDouble()
}

if (!path.startsWith("file://") && !path.startsWith("content://") && type == ResultType.IMAGE)
path = "file://$path"

val media = Result(
path,
fileName = item.fileName,
localIdentifier = item.id.toString(),
width,
height,
Expand All @@ -533,10 +607,12 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
realPath = item.realPath,
parentFolderName = item.parentFolderName,
creationDate = item.dateAddedTime.toDouble(),
crop = item.isCut,
path,
type,
duration = item.duration.toDouble(),
thumbnail = item.videoThumbnailPath,
crop = item.isCut
fileName = item.fileName,
thumbnail = thumbnail,
duration = item.duration.toDouble()
)

return media
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.margelo.nitro.multipleimagepicker

import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.luck.picture.lib.interfaces.OnKeyValueResultCallbackListener
import com.luck.picture.lib.interfaces.OnVideoThumbnailEventListener
import com.luck.picture.lib.utils.PictureFileUtils
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException


class VideoThumbnailEngine(private val targetPath: String) : OnVideoThumbnailEventListener {
override fun onVideoThumbnail(
context: Context, videoPath: String, call: OnKeyValueResultCallbackListener
) {
Glide.with(context).asBitmap().sizeMultiplier(0.6f).load(videoPath)
.into(object : CustomTarget<Bitmap?>() {
override fun onResourceReady(
resource: Bitmap, transition: Transition<in Bitmap?>?
) {
val stream = ByteArrayOutputStream()
resource.compress(Bitmap.CompressFormat.JPEG, 60, stream)
var fos: FileOutputStream? = null
var result: String? = null
try {
val targetFile =
File(targetPath, "thumbnails_" + System.currentTimeMillis() + ".jpg")
fos = FileOutputStream(targetFile)
fos.write(stream.toByteArray())
fos.flush()
result = targetFile.absolutePath
} catch (e: IOException) {
e.printStackTrace()
} finally {
PictureFileUtils.close(fos)
PictureFileUtils.close(stream)
}
call.onCallback(videoPath, result)
}

override fun onLoadCleared(placeholder: Drawable?) {
call.onCallback(videoPath, "")
}
})
}
}
Loading

0 comments on commit 52384bc

Please sign in to comment.