diff --git a/app/build.gradle b/app/build.gradle index 3659b08b..b965795a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,13 +63,13 @@ android { } dependencies { - implementation 'com.github.SimpleMobileTools:Simple-Commons:d1d5402388' + implementation 'com.github.SimpleMobileTools:Simple-Commons:7c48da6bef' implementation 'androidx.documentfile:documentfile:1.0.1' implementation "androidx.exifinterface:exifinterface:1.3.3" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.0" implementation 'androidx.window:window:1.1.0-alpha02' - def camerax_version = '1.1.0-rc02' + def camerax_version = '1.1.0' implementation "androidx.camera:camera-core:$camerax_version" implementation "androidx.camera:camera-camera2:$camerax_version" implementation "androidx.camera:camera-video:$camerax_version" diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/SettingsActivity.kt index 9989b0fa..a7114ca3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/SettingsActivity.kt @@ -10,10 +10,11 @@ import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.LICENSE_GLIDE +import com.simplemobiletools.commons.helpers.NavigationIcon import com.simplemobiletools.commons.models.FAQItem import com.simplemobiletools.commons.models.RadioItem +import java.util.Locale import kotlinx.android.synthetic.main.activity_settings.* -import java.util.* class SettingsActivity : SimpleActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -23,7 +24,7 @@ class SettingsActivity : SimpleActivity() { override fun onResume() { super.onResume() - + setupToolbar(settings_toolbar, NavigationIcon.Arrow) setupPurchaseThankYou() setupCustomizeColors() setupUseEnglish() diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/dialogs/ChangeResolutionDialog.kt b/app/src/main/kotlin/com/simplemobiletools/camera/dialogs/ChangeResolutionDialog.kt index 8b589cd2..ca6fb6b9 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/dialogs/ChangeResolutionDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/dialogs/ChangeResolutionDialog.kt @@ -8,13 +8,19 @@ import com.simplemobiletools.camera.activities.SimpleActivity import com.simplemobiletools.camera.extensions.config import com.simplemobiletools.camera.models.MySize import com.simplemobiletools.commons.dialogs.RadioGroupDialog +import com.simplemobiletools.commons.extensions.getAlertDialogBuilder import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.models.RadioItem -import kotlinx.android.synthetic.main.dialog_change_resolution.view.* +import kotlinx.android.synthetic.main.dialog_change_resolution.view.change_resolution_photo +import kotlinx.android.synthetic.main.dialog_change_resolution.view.change_resolution_photo_holder +import kotlinx.android.synthetic.main.dialog_change_resolution.view.change_resolution_video +import kotlinx.android.synthetic.main.dialog_change_resolution.view.change_resolution_video_holder -class ChangeResolutionDialog(val activity: SimpleActivity, val isFrontCamera: Boolean, val photoResolutions: ArrayList, - val videoResolutions: ArrayList, val openVideoResolutions: Boolean, val callback: () -> Unit) { - private var dialog: AlertDialog +class ChangeResolutionDialog( + val activity: SimpleActivity, val isFrontCamera: Boolean, val photoResolutions: ArrayList, + val videoResolutions: ArrayList, val openVideoResolutions: Boolean, val callback: () -> Unit +) { + private var dialog: AlertDialog? = null private val config = activity.config init { @@ -23,16 +29,18 @@ class ChangeResolutionDialog(val activity: SimpleActivity, val isFrontCamera: Bo setupVideoResolutionPicker(this) } - dialog = AlertDialog.Builder(activity) - .setPositiveButton(R.string.ok, null) - .setOnDismissListener { callback() } - .create().apply { - activity.setupDialogStuff(view, this, if (isFrontCamera) R.string.front_camera else R.string.back_camera) { - if (openVideoResolutions) { - view.change_resolution_video_holder.performClick() - } + activity.getAlertDialogBuilder() + .setPositiveButton(R.string.ok, null) + .setOnDismissListener { callback() } + .apply { + val titleId = if (isFrontCamera) R.string.front_camera else R.string.back_camera + activity.setupDialogStuff(view, this, titleId) { alertDialog -> + dialog = alertDialog + if (openVideoResolutions) { + view.change_resolution_video_holder.performClick() } } + } } private fun setupPhotoResolutionPicker(view: View) { @@ -49,7 +57,7 @@ class ChangeResolutionDialog(val activity: SimpleActivity, val isFrontCamera: Bo } else { config.backPhotoResIndex = it } - dialog.dismiss() + dialog?.dismiss() } } view.change_resolution_photo.text = items.getOrNull(selectionIndex)?.title @@ -68,7 +76,7 @@ class ChangeResolutionDialog(val activity: SimpleActivity, val isFrontCamera: Bo } else { config.backVideoResIndex = it } - dialog.dismiss() + dialog?.dismiss() } } view.change_resolution_video.text = items.getOrNull(selectionIndex)?.title diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/dialogs/ChangeResolutionDialogX.kt b/app/src/main/kotlin/com/simplemobiletools/camera/dialogs/ChangeResolutionDialogX.kt index 1b5c69a7..e0cefd2d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/dialogs/ChangeResolutionDialogX.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/dialogs/ChangeResolutionDialogX.kt @@ -10,6 +10,7 @@ import com.simplemobiletools.camera.extensions.config import com.simplemobiletools.camera.models.MySize import com.simplemobiletools.camera.models.VideoQuality import com.simplemobiletools.commons.dialogs.RadioGroupDialog +import com.simplemobiletools.commons.extensions.getAlertDialogBuilder import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.models.RadioItem import kotlinx.android.synthetic.main.dialog_change_resolution.view.change_resolution_photo @@ -22,9 +23,9 @@ class ChangeResolutionDialogX( private val isFrontCamera: Boolean, private val photoResolutions: List = listOf(), private val videoResolutions: List, - private val callback: () -> Unit + private val callback: () -> Unit, ) { - private var dialog: AlertDialog + private var dialog: AlertDialog? = null private val config = activity.config private val TAG = "ChangeResolutionDialogX" @@ -34,20 +35,27 @@ class ChangeResolutionDialogX( setupVideoResolutionPicker(this) } - dialog = AlertDialog.Builder(activity) + activity.getAlertDialogBuilder() .setPositiveButton(R.string.ok, null) - .create().apply { - activity.setupDialogStuff(view, this, if (isFrontCamera) R.string.front_camera else R.string.back_camera) + .apply { + val titleId = if (isFrontCamera) R.string.front_camera else R.string.back_camera + activity.setupDialogStuff(view, this, titleId) { alertDialog -> + dialog = alertDialog + } } } private fun setupPhotoResolutionPicker(view: View) { - val items = getFormattedResolutions(photoResolutions) + val items = photoResolutions.mapIndexed { index, resolution -> + val megapixels = resolution.megaPixels + val aspectRatio = resolution.getAspectRatio(activity) + RadioItem(index, "${resolution.width} x ${resolution.height} ($megapixels MP, $aspectRatio)") + } var selectionIndex = if (isFrontCamera) config.frontPhotoResIndex else config.backPhotoResIndex - selectionIndex = Math.max(selectionIndex, 0) + selectionIndex = selectionIndex.coerceAtLeast(0) view.change_resolution_photo_holder.setOnClickListener { - RadioGroupDialog(activity, items, selectionIndex) { + RadioGroupDialog(activity, ArrayList(items), selectionIndex) { selectionIndex = it as Int Log.w(TAG, "setupPhotoResolutionPicker: selectionIndex=$it") view.change_resolution_photo.text = items[selectionIndex].title @@ -56,7 +64,7 @@ class ChangeResolutionDialogX( } else { config.backPhotoResIndex = it } - dialog.dismiss() + dialog?.dismiss() callback.invoke() } } @@ -70,35 +78,27 @@ class ChangeResolutionDialogX( RadioItem(index, "${videoQuality.width} x ${videoQuality.height} ($megapixels MP, $aspectRatio)") } - val videoQuality = if (isFrontCamera) config.frontVideoQuality else config.backVideoQuality - var selectionIndex = videoResolutions.indexOf(videoQuality) + var selectionIndex = if (isFrontCamera) config.frontVideoResIndex else config.backVideoResIndex + selectionIndex = selectionIndex.coerceAtLeast(0) + Log.i(TAG, "videoResolutions=$videoResolutions") + Log.i(TAG, "setupVideoResolutionPicker: selectionIndex=$selectionIndex") view.change_resolution_video_holder.setOnClickListener { RadioGroupDialog(activity, ArrayList(items), selectionIndex) { selectionIndex = it as Int val selectedItem = items[selectionIndex] - val selectedQuality = videoResolutions[selectionIndex] view.change_resolution_video.text = selectedItem.title if (isFrontCamera) { - config.frontVideoQuality = selectedQuality + config.frontVideoResIndex = selectionIndex } else { - config.backVideoQuality = selectedQuality + config.backPhotoResIndex = selectionIndex } - dialog.dismiss() + dialog?.dismiss() callback.invoke() } } - view.change_resolution_video.text = items.getOrNull(selectionIndex)?.title - } - - private fun getFormattedResolutions(resolutions: List): ArrayList { - val items = ArrayList(resolutions.size) - val sorted = resolutions.sortedByDescending { it.width * it.height } - sorted.forEachIndexed { index, size -> - val megapixels = String.format("%.1f", (size.width * size.height.toFloat()) / 1000000) - val aspectRatio = size.getAspectRatio(activity) - items.add(RadioItem(index, "${size.width} x ${size.height} ($megapixels MP, $aspectRatio)")) - } - return items + val selectedItem = items.getOrNull(selectionIndex) + view.change_resolution_video.text = selectedItem?.title + Log.i(TAG, "setupVideoResolutionPicker: selectedItem=$selectedItem") } } diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt index 6ae1a3ed..5ede2c12 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt @@ -3,7 +3,6 @@ package com.simplemobiletools.camera.helpers import android.content.Context import android.os.Environment import androidx.camera.core.CameraSelector -import com.simplemobiletools.camera.models.VideoQuality import com.simplemobiletools.commons.helpers.BaseConfig import java.io.File @@ -63,20 +62,6 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getInt(FRONT_PHOTO_RESOLUTION_INDEX, 0) set(frontPhotoResIndex) = prefs.edit().putInt(FRONT_PHOTO_RESOLUTION_INDEX, frontPhotoResIndex).apply() - var backVideoQuality: VideoQuality - get() { - val backQuality = prefs.getString(BACK_VIDEO_QUALITY, VideoQuality.UHD.name) - return VideoQuality.values().first { it.name == backQuality } - } - set(backVideoQuality) = prefs.edit().putString(BACK_VIDEO_QUALITY, backVideoQuality.name).apply() - - var frontVideoQuality: VideoQuality - get() { - val frontQuality = prefs.getString(FRONT_VIDEO_QUALITY, VideoQuality.UHD.name) - return VideoQuality.values().first { it.name == frontQuality } - } - set(frontVideoQuality) = prefs.edit().putString(FRONT_VIDEO_QUALITY, frontVideoQuality.name).apply() - var frontVideoResIndex: Int get() = prefs.getInt(FRONT_VIDEO_RESOLUTION_INDEX, 0) set(frontVideoResIndex) = prefs.edit().putInt(FRONT_VIDEO_RESOLUTION_INDEX, frontVideoResIndex).apply() diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ImageQualityManager.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ImageQualityManager.kt index bad6f719..dda22ed7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ImageQualityManager.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ImageQualityManager.kt @@ -10,11 +10,8 @@ import android.util.Log import android.util.Size import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector -import androidx.camera.video.Quality import com.simplemobiletools.camera.extensions.config -import com.simplemobiletools.camera.extensions.toCameraXQuality import com.simplemobiletools.camera.models.CameraSelectorImageQualities -import com.simplemobiletools.camera.models.CameraSelectorVideoQualities import com.simplemobiletools.camera.models.MySize class ImageQualityManager( @@ -69,7 +66,7 @@ class ImageQualityManager( val index = if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) config.frontPhotoResIndex else config.backPhotoResIndex return imageQualities.filter { it.camSelector == cameraSelector } .flatMap { it.qualities } - .sortedByDescending { it.pixels} + .sortedByDescending { it.pixels } .distinctBy { it.pixels } .map { Size(it.width, it.height) } .also { @@ -85,5 +82,6 @@ class ImageQualityManager( .flatMap { it.qualities } .sortedByDescending { it.pixels } .distinctBy { it.pixels } + .filter { it.megaPixels != "0.0" } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/VideoQualityManager.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/VideoQualityManager.kt index 26f3dc3b..a2a6948e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/VideoQualityManager.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/VideoQualityManager.kt @@ -1,17 +1,20 @@ package com.simplemobiletools.camera.helpers import android.util.Log -import androidx.camera.core.Camera +import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.video.Quality import androidx.camera.video.QualitySelector +import com.simplemobiletools.camera.extensions.config import com.simplemobiletools.camera.extensions.toCameraXQuality import com.simplemobiletools.camera.extensions.toVideoQuality import com.simplemobiletools.camera.models.CameraSelectorVideoQualities import com.simplemobiletools.camera.models.VideoQuality -class VideoQualityManager(private val config: Config) { +class VideoQualityManager( + private val activity: AppCompatActivity, +) { companion object { private const val TAG = "VideoQualityHelper" @@ -19,14 +22,14 @@ class VideoQualityManager(private val config: Config) { private val CAMERA_SELECTORS = arrayOf(CameraSelector.DEFAULT_BACK_CAMERA, CameraSelector.DEFAULT_FRONT_CAMERA) } + private val config = activity.config private val videoQualities = mutableListOf() - fun initSupportedQualities( - cameraProvider: ProcessCameraProvider, - camera: Camera, - ) { + fun initSupportedQualities(cameraProvider: ProcessCameraProvider) { if (videoQualities.isEmpty()) { for (camSelector in CAMERA_SELECTORS) { + cameraProvider.unbindAll() + val camera = cameraProvider.bindToLifecycle(activity, camSelector) try { if (cameraProvider.hasCamera(camSelector)) { QualitySelector.getSupportedQualities(camera.cameraInfo) @@ -34,6 +37,7 @@ class VideoQualityManager(private val config: Config) { .also { allQualities -> val qualities = allQualities.map { it.toVideoQuality() } videoQualities.add(CameraSelectorVideoQualities(camSelector, qualities)) + } Log.i(TAG, "bindCameraUseCases: videoQualities=$videoQualities") } @@ -45,11 +49,9 @@ class VideoQualityManager(private val config: Config) { } fun getUserSelectedQuality(cameraSelector: CameraSelector): Quality { - return if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) { - config.frontVideoQuality.toCameraXQuality() - } else { - config.backVideoQuality.toCameraXQuality() - } + var selectionIndex = if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) config.frontVideoResIndex else config.backVideoResIndex + selectionIndex = selectionIndex.coerceAtLeast(0) + return getSupportedQualities(cameraSelector)[selectionIndex].toCameraXQuality() } fun getSupportedQualities(cameraSelector: CameraSelector): List { diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt index 89aa6499..8a12169b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt @@ -5,6 +5,7 @@ import android.content.Context import android.hardware.SensorManager import android.hardware.display.DisplayManager import android.util.Log +import android.util.Size import android.view.* import android.view.GestureDetector.SimpleOnGestureListener import androidx.appcompat.app.AppCompatActivity @@ -57,7 +58,7 @@ class CameraXPreview( private val displayManager = activity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager private val mediaSoundHelper = MediaSoundHelper() private val windowMetricsCalculator = WindowMetricsCalculator.getOrCreate() - private val videoQualityManager = VideoQualityManager(config) + private val videoQualityManager = VideoQualityManager(activity) private val imageQualityManager = ImageQualityManager(activity) private val exifRemover = ExifRemover(contentResolver) @@ -74,7 +75,7 @@ class CameraXPreview( in 225 until 315 -> Surface.ROTATION_90 else -> Surface.ROTATION_0 } - + Log.i(TAG, "onOrientationChanged: rotation=$rotation") preview?.targetRotation = rotation imageCapture?.targetRotation = rotation videoCapture?.targetRotation = rotation @@ -109,10 +110,13 @@ class CameraXPreview( private fun startCamera(switching: Boolean = false) { Log.i(TAG, "startCamera: ") imageQualityManager.initSupportedQualities() + val cameraProviderFuture = ProcessCameraProvider.getInstance(activity) cameraProviderFuture.addListener({ try { - cameraProvider = cameraProviderFuture.get() + val provider = cameraProviderFuture.get() + cameraProvider = provider + videoQualityManager.initSupportedQualities(provider) bindCameraUseCases() setupCameraObservers() } catch (e: Exception) { @@ -142,9 +146,7 @@ class CameraXPreview( cameraSelector, preview, captureUseCase, - ).also { - videoQualityManager.initSupportedQualities(cameraProvider, it) - } + ) preview?.setSurfaceProvider(previewView.surfaceProvider) setupZoomAndFocus() @@ -187,20 +189,31 @@ class CameraXPreview( } private fun buildImageCapture(aspectRatio: Int, rotation: Int): ImageCapture { - return ImageCapture.Builder() + return Builder() .setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY) .setFlashMode(flashMode) .setJpegQuality(config.photoQuality) .setTargetRotation(rotation) .apply { imageQualityManager.getUserSelectedResolution(cameraSelector)?.let { resolution -> + val rotatedResolution = getRotatedResolution(rotation, resolution) + Log.i(TAG, "buildImageCapture: rotation=$rotation") Log.i(TAG, "buildImageCapture: resolution=$resolution") - setTargetResolution(resolution) + Log.i(TAG, "buildImageCapture: rotatedResolution=$rotatedResolution") + setTargetResolution(rotatedResolution) } ?: setTargetAspectRatio(aspectRatio) } .build() } + private fun getRotatedResolution(rotationDegrees: Int, resolution: Size): Size { + return if (rotationDegrees == Surface.ROTATION_0 || rotationDegrees == Surface.ROTATION_180) { + Size(resolution.height, resolution.width) + } else { + Size(resolution.width, resolution.height) + } + } + private fun buildPreview(aspectRatio: Int, rotation: Int): Preview { return Preview.Builder() .setTargetRotation(rotation) @@ -211,7 +224,7 @@ class CameraXPreview( private fun buildVideoCapture(): VideoCapture { val qualitySelector = QualitySelector.from( videoQualityManager.getUserSelectedQuality(cameraSelector), - FallbackStrategy.lowerQualityOrHigherThan(Quality.SD), + FallbackStrategy.higherQualityOrLowerThan(Quality.SD), ) val recorder = Recorder.Builder() .setQualitySelector(qualitySelector) @@ -350,7 +363,7 @@ class CameraXPreview( if (bitmap != null) { listener.onImageCaptured(bitmap) } else { - cameraErrorHandler.handleImageCaptureError(ImageCapture.ERROR_CAPTURE_FAILED) + cameraErrorHandler.handleImageCaptureError(ERROR_CAPTURE_FAILED) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/models/MySize.kt b/app/src/main/kotlin/com/simplemobiletools/camera/models/MySize.kt index d1a77804..bd9702ea 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/models/MySize.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/models/MySize.kt @@ -5,8 +5,16 @@ import android.util.Size import com.simplemobiletools.camera.R data class MySize(val width: Int, val height: Int) { + companion object { + private const val ONE_MEGA_PIXEL = 1000000 + } + val ratio = width / height.toFloat() + val pixels: Int = width * height + + val megaPixels: String = String.format("%.1f", (width * height.toFloat()) / ONE_MEGA_PIXEL) + fun isSixteenToNine() = ratio == 16 / 9f private fun isFiveToThree() = ratio == 5 / 3f diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index f6ad4922..ef74273d 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -1,254 +1,283 @@ - + android:layout_height="match_parent"> - + android:layout_height="wrap_content"> - + android:layout_height="?attr/actionBarSize" + android:background="@color/color_primary" + app:layout_scrollFlags="scroll|enterAlways" + app:title="@string/settings" + app:titleTextAppearance="@style/AppTheme.ActionBar.TitleTextStyle" /> + + + + + - + android:text="@string/color_customization" /> - + + + android:background="@drawable/ripple_all_corners"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +