From df7966df52798642d915edd7669f9fb672cb37ae Mon Sep 17 00:00:00 2001 From: Jwalant Mehta Date: Tue, 8 Apr 2025 16:02:35 +0530 Subject: [PATCH 1/5] - Solved the issue of "Crop Aspect Ratio Not Working When Capturing Image from Camera" which was raised on github. --- .../presentation/camera/CameraFragment.kt | 60 +++++++++++++++---- .../LassiMediaPickerActivity.kt | 5 +- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt b/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt index 26ee293..2e559e3 100644 --- a/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt +++ b/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt @@ -8,6 +8,7 @@ import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.provider.Settings +import android.util.Log import android.view.LayoutInflater import android.view.View import androidx.activity.result.contract.ActivityResultContracts @@ -51,12 +52,13 @@ import com.lassi.presentation.mediadirectory.FolderViewModel import com.lassi.presentation.mediadirectory.FolderViewModelFactory import com.lassi.presentation.mediadirectory.SelectedMediaViewModelFactory import java.io.File +import kotlin.math.log class CameraFragment : LassiBaseViewModelFragment(), View.OnClickListener { private lateinit var cameraMode: Mode - + private val config = LassiConfig.getConfig() private val cameraViewModel by lazy { ViewModelProvider( this, SelectedMediaViewModelFactory(requireContext()) @@ -206,6 +208,36 @@ class CameraFragment : LassiBaseViewModelFragment +// CropImageOptions( +// imageSourceIncludeCamera = it, +// imageSourceIncludeGallery = includeGallery, +// cropShape = CropImageView.CropShape.RECTANGLE, +// showCropOverlay = true, +// guidelines = CropImageView.Guidelines.ON, +// multiTouchEnabled = false, +// outputCompressQuality = LassiConfig.getConfig().compressionRatio +// ) +// } +// }?.let { +// CropImageContractOptions( +// uri = uri, +// cropImageOptions = it, +// ) +// } +// ) +// } + private fun croppingOptions( uri: Uri? = null, includeCamera: Boolean? = false, @@ -217,15 +249,22 @@ class CameraFragment : LassiBaseViewModelFragment - CropImageOptions( - imageSourceIncludeCamera = it, - imageSourceIncludeGallery = includeGallery, - cropShape = CropImageView.CropShape.RECTANGLE, - showCropOverlay = true, - guidelines = CropImageView.Guidelines.ON, - multiTouchEnabled = false, - outputCompressQuality = LassiConfig.getConfig().compressionRatio - ) + config.cropAspectRatio?.x?.let { x -> + config.cropAspectRatio?.y?.let { y -> + CropImageOptions( + imageSourceIncludeCamera = it, + imageSourceIncludeGallery = includeGallery, + cropShape = config.cropType, + showCropOverlay = true, + guidelines = CropImageView.Guidelines.ON, + multiTouchEnabled = false, + aspectRatioX = x, + aspectRatioY = y, + fixAspectRatio = config.enableActualCircleCrop, + outputCompressQuality = config.compressionRatio + ) + } + } } }?.let { CropImageContractOptions( @@ -491,5 +530,6 @@ class CameraFragment : LassiBaseViewModelFragment initCamera() + R.id.menuCamera -> { + config.maxCount = 1 + initCamera() + } R.id.menuDone -> setSelectedMediaResult() android.R.id.home -> onBackPressedDispatcher.onBackPressed() } From 9a195da2d8b3855c2b005b14b8ecdb704e52de55 Mon Sep 17 00:00:00 2001 From: Jwalant Mehta Date: Wed, 9 Apr 2025 14:07:03 +0530 Subject: [PATCH 2/5] - Solved the issue of "Crop Aspect Ratio Not Working When Capturing Image from Camera" which was raised on github. --- app/src/main/java/com/lassi/app/MainActivity.kt | 6 +++--- lassi/src/main/java/com/lassi/domain/media/LassiConfig.kt | 2 +- lassi/src/main/java/com/lassi/domain/media/LassiOption.kt | 3 ++- .../java/com/lassi/presentation/camera/CameraFragment.kt | 5 +++-- .../presentation/mediadirectory/LassiMediaPickerActivity.kt | 5 +---- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/lassi/app/MainActivity.kt b/app/src/main/java/com/lassi/app/MainActivity.kt index d14b259..19e092b 100644 --- a/app/src/main/java/com/lassi/app/MainActivity.kt +++ b/app/src/main/java/com/lassi/app/MainActivity.kt @@ -216,7 +216,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { } R.id.btnPhotoVideoPicker -> { - val intent = lassi.with(LassiOption.CAMERA_AND_GALLERY).setMaxCount(4) + val intent = lassi.with(LassiOption.PICKER).setMaxCount(4) .setMediaType(MediaType.PHOTO_VIDEO_PICKER) .setStatusBarColor(R.color.colorPrimaryDark) .setToolbarColor(R.color.colorPrimary) @@ -229,7 +229,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { } R.id.btnPhotoPicker -> { - val intent = lassi.with(LassiOption.CAMERA_AND_GALLERY).setMaxCount(4) + val intent = lassi.with(LassiOption.PICKER).setMaxCount(4) .setAscSort(SortingOption.ASCENDING).setGridSize(2) .setMediaType(MediaType.PHOTO_PICKER) .setPlaceHolder(R.drawable.ic_image_placeholder) @@ -249,7 +249,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { } R.id.btnVideoMediaPicker -> { - val intent = lassi.with(LassiOption.CAMERA_AND_GALLERY).setMaxCount(4) + val intent = lassi.with(LassiOption.PICKER).setMaxCount(4) .setMediaType(MediaType.VIDEO_PICKER) .setStatusBarColor(R.color.colorPrimaryDark) .setToolbarColor(R.color.colorPrimary) diff --git a/lassi/src/main/java/com/lassi/domain/media/LassiConfig.kt b/lassi/src/main/java/com/lassi/domain/media/LassiConfig.kt index 3d1eeef..1e6f2f4 100644 --- a/lassi/src/main/java/com/lassi/domain/media/LassiConfig.kt +++ b/lassi/src/main/java/com/lassi/domain/media/LassiConfig.kt @@ -31,7 +31,7 @@ data class LassiConfig( var maxCount: Int = KeyUtils.DEFAULT_MEDIA_COUNT, var ascFlag: Int = KeyUtils.DEFAULT_ORDER, var gridSize: Int = KeyUtils.DEFAULT_GRID_SIZE, - var lassiOption: LassiOption = LassiOption.CAMERA_AND_GALLERY, + var lassiOption: LassiOption = LassiOption.PICKER, var minTime: Long = KeyUtils.DEFAULT_DURATION, var maxTime: Long = KeyUtils.DEFAULT_DURATION, var cropType: CropImageView.CropShape = CropImageView.CropShape.RECTANGLE, diff --git a/lassi/src/main/java/com/lassi/domain/media/LassiOption.kt b/lassi/src/main/java/com/lassi/domain/media/LassiOption.kt index 8e13392..16e3cdb 100644 --- a/lassi/src/main/java/com/lassi/domain/media/LassiOption.kt +++ b/lassi/src/main/java/com/lassi/domain/media/LassiOption.kt @@ -3,5 +3,6 @@ package com.lassi.domain.media enum class LassiOption { CAMERA, GALLERY, - CAMERA_AND_GALLERY + CAMERA_AND_GALLERY, + PICKER } \ No newline at end of file diff --git a/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt b/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt index 2e559e3..bfa7e60 100644 --- a/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt +++ b/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt @@ -64,6 +64,8 @@ class CameraFragment : LassiBaseViewModelFragment val config = LassiConfig.getConfig() - if (config.isCrop && config.maxCount <= 1) { + if (config.isCrop && cameraMaxCount <= 1) { croppingOptions(uri) } else { val mediaList = arrayListOf(createMiMedia(uri.path)) @@ -530,6 +532,5 @@ class CameraFragment : LassiBaseViewModelFragment { - config.maxCount = 1 - initCamera() - } + R.id.menuCamera -> initCamera() R.id.menuDone -> setSelectedMediaResult() android.R.id.home -> onBackPressedDispatcher.onBackPressed() } From 299a1b141c0825028170afbbc698b14c6a1dec98 Mon Sep 17 00:00:00 2001 From: Jwalant Mehta Date: Wed, 9 Apr 2025 15:06:10 +0530 Subject: [PATCH 3/5] - Solved the misleading combination issue raised on github. --- .../com/lassi/presentation/mediadirectory/FolderFragment.kt | 2 +- .../presentation/mediadirectory/LassiMediaPickerActivity.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lassi/src/main/java/com/lassi/presentation/mediadirectory/FolderFragment.kt b/lassi/src/main/java/com/lassi/presentation/mediadirectory/FolderFragment.kt index d0818fd..ab56da7 100644 --- a/lassi/src/main/java/com/lassi/presentation/mediadirectory/FolderFragment.kt +++ b/lassi/src/main/java/com/lassi/presentation/mediadirectory/FolderFragment.kt @@ -548,7 +548,7 @@ class FolderFragment : LassiBaseViewModelFragment Date: Wed, 13 Aug 2025 15:37:55 +0530 Subject: [PATCH 4/5] - Solved the issue #126 "Misleading combination of LassiOption.CAMERA_AND_GALLERY and MediaType.PHOTO_VIDEO_PICKER". - Solved the issue #128 "Crop Aspect Ratio Not Working When Capturing Image from Camera". --- .../com/lassi/presentation/builder/Lassi.kt | 3 + .../presentation/camera/CameraFragment.kt | 221 ++++++++++-------- 2 files changed, 128 insertions(+), 96 deletions(-) diff --git a/lassi/src/main/java/com/lassi/presentation/builder/Lassi.kt b/lassi/src/main/java/com/lassi/presentation/builder/Lassi.kt index 23821c4..2b04031 100644 --- a/lassi/src/main/java/com/lassi/presentation/builder/Lassi.kt +++ b/lassi/src/main/java/com/lassi/presentation/builder/Lassi.kt @@ -197,6 +197,9 @@ class Lassi(private val context: Context) { * Allow Media picket to capture/record from camera while multiple media selection */ fun with(lassiOption: LassiOption): Lassi { + // Reset config to defaults for every new build chain to avoid leaking + // previous session state (e.g., isCrop from disableCrop()). + lassiConfig = LassiConfig() lassiConfig.lassiOption = lassiOption return this } diff --git a/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt b/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt index bfa7e60..9a81ee3 100644 --- a/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt +++ b/lassi/src/main/java/com/lassi/presentation/camera/CameraFragment.kt @@ -52,11 +52,11 @@ import com.lassi.presentation.mediadirectory.FolderViewModel import com.lassi.presentation.mediadirectory.FolderViewModelFactory import com.lassi.presentation.mediadirectory.SelectedMediaViewModelFactory import java.io.File -import kotlin.math.log class CameraFragment : LassiBaseViewModelFragment(), View.OnClickListener { + private val TAG: String = "CameraFragment" private lateinit var cameraMode: Mode private val config = LassiConfig.getConfig() private val cameraViewModel by lazy { @@ -64,30 +64,97 @@ class CameraFragment : LassiBaseViewModelFragment = ArrayList() + private var mediaList: ArrayList = ArrayList() + private var selected = + LassiConfig.getConfig().selectedMedias // this gives the gallery selected images. + private var isFromCropNext = false - private val startVideoContract = registerForActivityResult(StartVideoContract()) { miMedia -> + private val cropImage = registerForActivityResult(CropImageContract()) { miMedia -> if (LassiConfig.isSingleMediaSelection()) { + isFromCropNext = true miMedia?.let { setResultOk(arrayListOf(it)) } + return@registerForActivityResult + } + + if (miMedia != null) { + // Replace cropped into selected + selected[currentCropIndex] = miMedia + + // Compress the cropped image + val compressed = compressSingleMedia(miMedia) + selected[currentCropIndex] = compressed + + croppedMediaList.add(compressed) + } + cropNext() + } + + private fun getSelectedAndCapturedImages() { + selected = config.selectedMedias + selected.addAll(mediaList) + } + + private fun startCroppingSequence() { + if (selected.isNotEmpty()) { + currentCropIndex = 0 + croppedMediaList.clear() + val firstPath = selected[0].path + if (!firstPath.isNullOrEmpty()) { + val uri = Uri.fromFile(File(firstPath)) + croppingOptions(uri) + } else { + // skip invalid first path + cropImage.launch(null) + } } else { - LassiConfig.getConfig().selectedMedias.add(miMedia!!) - cameraViewModel.addSelectedMedia(miMedia) - folderViewModel.checkInsert() - if (LassiConfig.getConfig().lassiOption == LassiOption.CAMERA_AND_GALLERY || LassiConfig.getConfig().lassiOption == LassiOption.GALLERY) { - setResultOk(arrayListOf(miMedia)) + Log.d(TAG, "startCroppingSequence: selected is empty") + } + } + + private fun cropNext() { + currentCropIndex++ + if (currentCropIndex < selected.size) { + val path = selected[currentCropIndex].path + if (!path.isNullOrEmpty()) { + val uri = Uri.fromFile(File(path)) + croppingOptions(uri) + } else { + cropNext() } + } else { + isFromCropNext = true + setResultOk(selected) } } - private val cropImage = registerForActivityResult(CropImageContract()) { miMedia -> + private fun compressSingleMedia(miMedia: MiMedia): MiMedia { + miMedia.path?.let { path -> + val uri = Uri.fromFile(File(path)) + val compressFormat = getCompressFormatForUri(uri, requireContext()) + val newUri = writeBitmapToUri( + requireContext(), + decodeUriToBitmap(requireContext(), uri), + compressQuality = LassiConfig.getConfig().compressionRatio, + customOutputUri = null, + compressFormat = compressFormat + ) + return miMedia.copy(path = newUri.path) + } + return miMedia // return original if path is null + } + + private val folderViewModel by lazy { + ViewModelProvider( + this, FolderViewModelFactory(requireContext()) + )[FolderViewModel::class.java] + } + + private val startVideoContract = registerForActivityResult(StartVideoContract()) { miMedia -> if (LassiConfig.isSingleMediaSelection()) { + isFromCropNext = true miMedia?.let { setResultOk(arrayListOf(it)) } } else { LassiConfig.getConfig().selectedMedias.add(miMedia!!) @@ -95,7 +162,6 @@ class CameraFragment : LassiBaseViewModelFragment val config = LassiConfig.getConfig() - if (config.isCrop && cameraMaxCount <= 1) { - croppingOptions(uri) - } else { - val mediaList = arrayListOf(createMiMedia(uri.path)) - if (config.compressionRatio > 0) { - compressMedia(mediaList) - } else { - setResultOk(mediaList) - } + + mediaList = arrayListOf(createMiMedia(uri.path)) + croppedMediaList.addAll(config.selectedMedias + mediaList) + if (config.compressionRatio > 0 && !config.isCrop) { + compressMedia(croppedMediaList) + } else { // user has selected the crop option. + setResultOk(croppedMediaList) } }) } @@ -210,73 +274,27 @@ class CameraFragment : LassiBaseViewModelFragment -// CropImageOptions( -// imageSourceIncludeCamera = it, -// imageSourceIncludeGallery = includeGallery, -// cropShape = CropImageView.CropShape.RECTANGLE, -// showCropOverlay = true, -// guidelines = CropImageView.Guidelines.ON, -// multiTouchEnabled = false, -// outputCompressQuality = LassiConfig.getConfig().compressionRatio -// ) -// } -// }?.let { -// CropImageContractOptions( -// uri = uri, -// cropImageOptions = it, -// ) -// } -// ) -// } - - private fun croppingOptions( - uri: Uri? = null, - includeCamera: Boolean? = false, - includeGallery: Boolean? = false - ) { - /** - * Start picker to get image for cropping and then use the image in cropping activity. - */ - cropImage.launch( - includeCamera?.let { - includeGallery?.let { includeGallery -> - config.cropAspectRatio?.x?.let { x -> - config.cropAspectRatio?.y?.let { y -> - CropImageOptions( - imageSourceIncludeCamera = it, - imageSourceIncludeGallery = includeGallery, - cropShape = config.cropType, - showCropOverlay = true, - guidelines = CropImageView.Guidelines.ON, - multiTouchEnabled = false, - aspectRatioX = x, - aspectRatioY = y, - fixAspectRatio = config.enableActualCircleCrop, - outputCompressQuality = config.compressionRatio - ) - } - } - } - }?.let { - CropImageContractOptions( - uri = uri, - cropImageOptions = it, - ) - } + private fun croppingOptions(uri: Uri) { + val config = LassiConfig.getConfig() + val aspectX: Int = config.cropAspectRatio?.x ?: return + val aspectY: Int = config.cropAspectRatio?.y ?: return + + val cropOptions = CropImageOptions( + imageSourceIncludeCamera = false, + imageSourceIncludeGallery = false, + cropShape = config.cropType, + showCropOverlay = true, + guidelines = CropImageView.Guidelines.ON, + multiTouchEnabled = false, + aspectRatioX = aspectX, + aspectRatioY = aspectY, + fixAspectRatio = config.enableActualCircleCrop, + outputCompressQuality = config.compressionRatio ) - } + val contractOptions = CropImageContractOptions(uri, cropOptions) + cropImage.launch(contractOptions) + } private fun toggleCamera() { binding.apply { @@ -301,6 +319,7 @@ class CameraFragment : LassiBaseViewModelFragment toggleCamera() R.id.ivFlash -> { //Check whether the flashlight is available or not? @@ -449,14 +468,12 @@ class CameraFragment : LassiBaseViewModelFragment?) { - val intent = Intent().apply { - putExtra(KeyUtils.SELECTED_MEDIA, selectedMedia) + if (isFromCropNext || !config.isCrop) { // the !config.isCrop condition is given as when the crop is been disabled from the main activity, we don't go to the else part. + /** + * this will be when calling from the cropNext() + */ + val intent = Intent().apply { + putExtra(KeyUtils.SELECTED_MEDIA, selectedMedia) + } + activity?.setResult(Activity.RESULT_OK, intent) + activity?.finish() + isFromCropNext = false + } else { + /** + * everytime else + */ + getSelectedAndCapturedImages() + startCroppingSequence() } - activity?.setResult(Activity.RESULT_OK, intent) - activity?.finish() } private fun requestForPermissions() { From b29a4aa471513f20a47d3e78bbb17db85be1685a Mon Sep 17 00:00:00 2001 From: Jwalant Mehta Date: Wed, 13 Aug 2025 17:22:29 +0530 Subject: [PATCH 5/5] - Updated the README.md for the issue #126 & #128. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a665e1..b3d923b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Lassi is simplest way to pick media (either image, video, audio or doc) * Enable/disable camera from LassiOption * You can open System Default view for file selection by using MediaType.FILE_TYPE_WITH_SYSTEM_VIEW * Photo Picker feature integration +* Group picked album images with camera capture for editing # Usage @@ -65,7 +66,7 @@ Lassi is simplest way to pick media (either image, video, audio or doc) ```kotlin val intent = Lassi(this) - .with(LassiOption.CAMERA_AND_GALLERY) // choose Option CAMERA, GALLERY or CAMERA_AND_GALLERY + .with(LassiOption.CAMERA_AND_GALLERY) // choose Option CAMERA, GALLERY, CAMERA_AND_GALLERY or PICKER .setMaxCount(5) .setGridSize(3) .setMediaType(MediaType.VIDEO) // MediaType : VIDEO IMAGE, AUDIO OR DOC