From f6235e3b96fb05441987991c0ad312ed39a0dfbe Mon Sep 17 00:00:00 2001 From: darthpaul Date: Sat, 23 Jul 2022 13:12:05 +0100 Subject: [PATCH 01/23] always show the navigation bar --- .../camera/activities/MainActivity.kt | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index a85f5d43..78c7ac1c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -51,12 +51,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera var mLastHandledOrientation = 0 override fun onCreate(savedInstanceState: Bundle?) { - window.addFlags( - WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or - WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or - WindowManager.LayoutParams.FLAG_FULLSCREEN - ) useDynamicTheme = false super.onCreate(savedInstanceState) appLaunched(BuildConfig.APPLICATION_ID) @@ -67,6 +61,22 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera supportActionBar?.hide() checkWhatsNewDialog() setupOrientationEventListener() + if (isRPlus()) { + setShowWhenLocked(true) + setTurnScreenOn(true) + window.insetsController?.hide(WindowInsets.Type.statusBars()) + } else if (isOreoMr1Plus()) { + setShowWhenLocked(true) + setTurnScreenOn(true) + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + } else { + window.addFlags( + WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or + WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + } } override fun onResume() { @@ -433,15 +443,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera view.isClickable = value != .0f } - @Suppress("DEPRECATION") - private fun hideNavigationBarIcons() { - if (isRPlus()) { - window.insetsController?.hide(WindowInsets.Type.systemBars()) - } else { - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE - } - } - private fun showTimer() { video_rec_curr_timer.beVisible() setupTimer() @@ -464,8 +465,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } private fun resumeCameraItems() { - hideNavigationBarIcons() - if (!mIsInPhotoMode) { initVideoButtons() } From d9193f74945c81bd2c652f728839f5ffc9cce62e Mon Sep 17 00:00:00 2001 From: darthpaul Date: Wed, 17 Aug 2022 01:12:18 +0100 Subject: [PATCH 02/23] fix preview and capture resolutions - adjust the preview so the image captured is what the user sees - add method MySize.isSupported; current support is for full, 16:9, 4:3 and 1:1 of the highest resolution - cleanup ImageQualityManager, add method to get the full screen resolution which is just the resolution with the largest width - change the UI to move settings to the top and remove the auto fading --- .../camera/activities/MainActivity.kt | 44 +---- .../camera/dialogs/ChangeResolutionDialogX.kt | 7 +- .../camera/helpers/Config.kt | 1 + .../camera/helpers/ImageQualityManager.kt | 38 ++-- .../camera/implementations/CameraXPreview.kt | 146 +++++++++------- .../simplemobiletools/camera/models/MySize.kt | 13 +- .../main/res/drawable/gradient_background.xml | 7 + .../drawable/gradient_background_flipped.xml | 7 + app/src/main/res/layout/activity_main.xml | 163 ++++++++++++------ 9 files changed, 249 insertions(+), 177 deletions(-) create mode 100644 app/src/main/res/drawable/gradient_background.xml create mode 100644 app/src/main/res/drawable/gradient_background_flipped.xml diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index 78c7ac1c..19660784 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -10,7 +10,7 @@ import android.os.Handler import android.os.Looper import android.provider.MediaStore import android.view.* -import android.widget.RelativeLayout +import androidx.constraintlayout.widget.ConstraintLayout import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions @@ -32,7 +32,6 @@ import kotlinx.android.synthetic.main.activity_main.* class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, CameraXPreviewListener { companion object { - private const val FADE_DELAY = 5000L private const val CAPTURE_ANIMATION_DURATION = 100L } @@ -84,7 +83,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera if (hasStorageAndCameraPermissions()) { resumeCameraItems() setupPreviewImage(mIsInPhotoMode) - scheduleFadeOut() mFocusCircleView.setStrokeColor(getProperPrimaryColor()) if (isVideoCaptureIntent() && mIsInPhotoMode) { @@ -216,11 +214,11 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera setContentView(R.layout.activity_main) initButtons() - (btn_holder.layoutParams as RelativeLayout.LayoutParams).setMargins( + (video_rec_curr_timer.layoutParams as ConstraintLayout.LayoutParams).setMargins( 0, 0, 0, - (navigationBarHeight + resources.getDimension(R.dimen.activity_margin)).toInt() + (navigationBarHeight + resources.getDimension(R.dimen.big_margin)).toInt() ) checkVideoCaptureIntent() @@ -310,12 +308,8 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } private fun launchSettings() { - if (settings.alpha == 1f) { - val intent = Intent(applicationContext, SettingsActivity::class.java) - startActivity(intent) - } else { - fadeInButtons() - } + val intent = Intent(applicationContext, SettingsActivity::class.java) + startActivity(intent) } private fun handleTogglePhotoVideo() { @@ -415,34 +409,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } } - private fun scheduleFadeOut() { - if (!config.keepSettingsVisible) { - mFadeHandler.postDelayed({ - fadeOutButtons() - }, FADE_DELAY) - } - } - - private fun fadeOutButtons() { - fadeAnim(settings, .5f) - fadeAnim(toggle_photo_video, .0f) - fadeAnim(change_resolution, .0f) - fadeAnim(last_photo_video_preview, .0f) - } - - private fun fadeInButtons() { - fadeAnim(settings, 1f) - fadeAnim(toggle_photo_video, 1f) - fadeAnim(change_resolution, 1f) - fadeAnim(last_photo_video_preview, 1f) - scheduleFadeOut() - } - - private fun fadeAnim(view: View, value: Float) { - view.animate().alpha(value).start() - view.isClickable = value != .0f - } - private fun showTimer() { video_rec_curr_timer.beVisible() setupTimer() 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 92e83085..cc04517e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/dialogs/ChangeResolutionDialogX.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/dialogs/ChangeResolutionDialogX.kt @@ -47,7 +47,12 @@ class ChangeResolutionDialogX( 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)") + if (resolution.isFullScreen) { + //TODO: Extract to string resource + RadioItem(index, "Full") + } else { + RadioItem(index, "${resolution.width} x ${resolution.height} ($megapixels MP, $aspectRatio)") + } } var selectionIndex = if (isFrontCamera) config.frontPhotoResIndex else config.backPhotoResIndex selectionIndex = selectionIndex.coerceAtLeast(0) 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 5ede2c12..567d3af1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt @@ -66,6 +66,7 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getInt(FRONT_VIDEO_RESOLUTION_INDEX, 0) set(frontVideoResIndex) = prefs.edit().putInt(FRONT_VIDEO_RESOLUTION_INDEX, frontVideoResIndex).apply() + //TODO: Remove keepSettingsVisible since the view has moved to the top var keepSettingsVisible: Boolean get() = prefs.getBoolean(KEEP_SETTINGS_VISIBLE, false) set(keepSettingsVisible) = prefs.edit().putBoolean(KEEP_SETTINGS_VISIBLE, keepSettingsVisible).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 b92e5867..741be610 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ImageQualityManager.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ImageQualityManager.kt @@ -28,13 +28,12 @@ class ImageQualityManager( for (cameraId in cameraManager.cameraIdList) { try { val characteristics = cameraManager.getCameraCharacteristics(cameraId) - for (lens in CAMERA_LENS) { - if (characteristics.get(CameraCharacteristics.LENS_FACING) == lens) { - val configMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue - val imageSizes = configMap.getOutputSizes(ImageFormat.JPEG).map { MySize(it.width, it.height) } - val cameraSelector = lens.toCameraSelector() - imageQualities.add(CameraSelectorImageQualities(cameraSelector, imageSizes)) - } + val lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING) ?: continue + if (lensFacing in CAMERA_LENS) { + val configMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue + val imageSizes = configMap.getOutputSizes(ImageFormat.JPEG).map { MySize(it.width, it.height) } + val cameraSelector = lensFacing.toCameraSelector() + imageQualities.add(CameraSelectorImageQualities(cameraSelector, imageSizes)) } } catch (e: Exception) { activity.showErrorToast(e) @@ -52,19 +51,26 @@ class ImageQualityManager( } fun getUserSelectedResolution(cameraSelector: CameraSelector): MySize { - val index = if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) config.frontPhotoResIndex else config.backPhotoResIndex - return imageQualities.filter { it.camSelector == cameraSelector } - .flatMap { it.qualities } - .sortedWith(compareByDescending { it.ratio }.thenByDescending { it.pixels }) - .distinctBy { it.pixels } - .filter { it.megaPixels != "0.0" }[index] + val resolutions = getSupportedResolutions(cameraSelector) + var index = if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) config.frontPhotoResIndex else config.backPhotoResIndex + index = index.coerceAtMost(resolutions.lastIndex) + return resolutions[index] } fun getSupportedResolutions(cameraSelector: CameraSelector): List { + val fullScreenSize = getFullScreenResolution(cameraSelector) + return listOf(fullScreenSize) + imageQualities.filter { it.camSelector == cameraSelector } + .flatMap { it.qualities } + .sortedByDescending { it.pixels } + .distinctBy { it.getAspectRatio(activity) } + .filter { it.isSupported() } + } + + private fun getFullScreenResolution(cameraSelector: CameraSelector): MySize { return imageQualities.filter { it.camSelector == cameraSelector } .flatMap { it.qualities } - .sortedWith(compareByDescending { it.ratio }.thenByDescending { it.pixels }) - .distinctBy { it.pixels } - .filter { it.megaPixels != "0.0" } + .sortedByDescending { it.width } + .first { it.isSupported() } + .copy(isFullScreen = true) } } 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 42a6927f..4b7ecbbc 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.Context import android.hardware.SensorManager import android.hardware.display.DisplayManager +import android.util.Rational import android.util.Size import android.view.* import android.view.GestureDetector.SimpleOnGestureListener @@ -14,10 +15,12 @@ import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.video.* import androidx.camera.video.VideoCapture import androidx.camera.view.PreviewView +import androidx.camera.view.PreviewView.ScaleType import androidx.core.content.ContextCompat import androidx.core.view.doOnLayout import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner +import androidx.window.layout.WindowMetricsCalculator import com.bumptech.glide.load.ImageHeaderParser.UNKNOWN_ORIENTATION import com.simplemobiletools.camera.R import com.simplemobiletools.camera.dialogs.ChangeResolutionDialogX @@ -49,6 +52,7 @@ class CameraXPreview( private val mainExecutor = ContextCompat.getMainExecutor(activity) private val displayManager = activity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager private val mediaSoundHelper = MediaSoundHelper() + private val windowMetricsCalculator = WindowMetricsCalculator.getOrCreate() private val videoQualityManager = VideoQualityManager(activity) private val imageQualityManager = ImageQualityManager(activity) private val exifRemover = ExifRemover(contentResolver) @@ -120,7 +124,7 @@ class CameraXPreview( private fun bindCameraUseCases() { val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.") - val rotation = previewView.display.rotation + val resolution = if (isPhotoCapture) { imageQualityManager.getUserSelectedResolution(cameraSelector) } else { @@ -128,22 +132,94 @@ class CameraXPreview( MySize(selectedQuality.width, selectedQuality.height) } + val isFullSize = resolution.isFullScreen + previewView.scaleType = if (isFullSize) ScaleType.FILL_CENTER else ScaleType.FIT_CENTER + val rotation = previewView.display.rotation val rotatedResolution = getRotatedResolution(resolution, rotation) - preview = buildPreview(rotatedResolution, rotation) + val previewUseCase = buildPreview(rotatedResolution, rotation) val captureUseCase = getCaptureUseCase(rotatedResolution, rotation) - cameraProvider.unbindAll() - camera = cameraProvider.bindToLifecycle( - activity, - cameraSelector, - preview, - captureUseCase, - ) - preview?.setSurfaceProvider(previewView.surfaceProvider) + cameraProvider.unbindAll() + camera = if (isFullSize) { + val metrics = windowMetricsCalculator.computeCurrentWindowMetrics(activity).bounds + val screenWidth = metrics.width() + val screenHeight = metrics.height() + val viewPort = ViewPort.Builder(Rational(screenWidth, screenHeight), rotation).build() + + val useCaseGroup = UseCaseGroup.Builder() + .addUseCase(previewUseCase) + .addUseCase(captureUseCase) + .setViewPort(viewPort) + .build() + + cameraProvider.bindToLifecycle( + activity, + cameraSelector, + useCaseGroup, + ) + } else { + cameraProvider.bindToLifecycle( + activity, + cameraSelector, + previewUseCase, + captureUseCase, + ) + } + + previewUseCase.setSurfaceProvider(previewView.surfaceProvider) + preview = previewUseCase setupZoomAndFocus() } + private fun getRotatedResolution(resolution: MySize, rotationDegrees: Int): 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(resolution: Size, rotation: Int): Preview { + return Preview.Builder() + .setTargetRotation(rotation) + .setTargetResolution(resolution) + .build() + } + + private fun getCaptureUseCase(resolution: Size, rotation: Int): UseCase { + return if (isPhotoCapture) { + buildImageCapture(resolution, rotation).also { + imageCapture = it + } + } else { + buildVideoCapture().also { + videoCapture = it + } + } + } + + private fun buildImageCapture(resolution: Size, rotation: Int): ImageCapture { + return Builder() + .setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY) + .setFlashMode(flashMode) + .setJpegQuality(config.photoQuality) + .setTargetRotation(rotation) + .setTargetResolution(resolution) + .build() + } + + private fun buildVideoCapture(): VideoCapture { + val qualitySelector = QualitySelector.from( + videoQualityManager.getUserSelectedQuality(cameraSelector).toCameraXQuality(), + FallbackStrategy.higherQualityOrLowerThan(Quality.SD), + ) + val recorder = Recorder.Builder() + .setQualitySelector(qualitySelector) + .build() + return VideoCapture.withOutput(recorder) + } + private fun setupCameraObservers() { listener.setFlashAvailable(camera?.cameraInfo?.hasFlashUnit() ?: false) listener.onChangeCamera(isFrontCameraInUse()) @@ -166,56 +242,6 @@ class CameraXPreview( } } - private fun getCaptureUseCase(resolution: Size, rotation: Int): UseCase { - return if (isPhotoCapture) { - cameraProvider?.unbind(videoCapture) - buildImageCapture(resolution, rotation).also { - imageCapture = it - } - } else { - cameraProvider?.unbind(imageCapture) - buildVideoCapture().also { - videoCapture = it - } - } - } - - private fun buildImageCapture(resolution: Size, rotation: Int): ImageCapture { - return Builder() - .setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY) - .setFlashMode(flashMode) - .setJpegQuality(config.photoQuality) - .setTargetRotation(rotation) - .setTargetResolution(resolution) - .build() - } - - private fun getRotatedResolution(resolution: MySize, rotationDegrees: Int): 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(resolution: Size, rotation: Int): Preview { - return Preview.Builder() - .setTargetRotation(rotation) - .setTargetResolution(resolution) - .build() - } - - private fun buildVideoCapture(): VideoCapture { - val qualitySelector = QualitySelector.from( - videoQualityManager.getUserSelectedQuality(cameraSelector).toCameraXQuality(), - FallbackStrategy.higherQualityOrLowerThan(Quality.SD), - ) - val recorder = Recorder.Builder() - .setQualitySelector(qualitySelector) - .build() - return VideoCapture.withOutput(recorder) - } - private fun hasBackCamera(): Boolean { return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false } 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 bd9702ea..3ad93eac 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/models/MySize.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/models/MySize.kt @@ -4,18 +4,19 @@ import android.content.Context import android.util.Size import com.simplemobiletools.camera.R -data class MySize(val width: Int, val height: Int) { +data class MySize(val width: Int, val height: Int, val isFullScreen: Boolean = false) { companion object { private const val ONE_MEGA_PIXEL = 1000000 + private const val ZERO_MEGA_PIXEL = "0.0" } - val ratio = width / height.toFloat() + private val ratio = width / height.toFloat() val pixels: Int = width * height - val megaPixels: String = String.format("%.1f", (width * height.toFloat()) / ONE_MEGA_PIXEL) + val megaPixels: String = String.format("%.1f", (width * height.toFloat()) / ONE_MEGA_PIXEL) - fun isSixteenToNine() = ratio == 16 / 9f + private fun isSixteenToNine() = ratio == 16 / 9f private fun isFiveToThree() = ratio == 5 / 3f @@ -37,6 +38,10 @@ data class MySize(val width: Int, val height: Int) { private fun isSquare() = width == height + fun isSupported(): Boolean { + return (isFourToThree() || isSixteenToNine() || isSquare()) && megaPixels != ZERO_MEGA_PIXEL + } + fun getAspectRatio(context: Context) = when { isSixteenToNine() -> "16:9" isFiveToThree() -> "5:3" diff --git a/app/src/main/res/drawable/gradient_background.xml b/app/src/main/res/drawable/gradient_background.xml new file mode 100644 index 00000000..68f8cc81 --- /dev/null +++ b/app/src/main/res/drawable/gradient_background.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/gradient_background_flipped.xml b/app/src/main/res/drawable/gradient_background_flipped.xml new file mode 100644 index 00000000..e8481212 --- /dev/null +++ b/app/src/main/res/drawable/gradient_background_flipped.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 7ed73b50..20cbc359 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,17 +1,17 @@ - + android:background="@android:color/black" + android:fitsSystemWindows="true"> + android:layout_height="match_parent" /> + + + android:src="@drawable/ic_settings_cog_vector" + app:layout_constraintEnd_toStartOf="@id/change_resolution" + app:layout_constraintHorizontal_chainStyle="spread" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + android:src="@drawable/ic_resolution_vector" + app:layout_constraintEnd_toStartOf="@id/toggle_flash" + app:layout_constraintStart_toEndOf="@id/settings" + app:layout_constraintTop_toTopOf="@id/settings" /> + + + + + + + + + + + + + + + android:padding="@dimen/medium_margin" + app:layout_constraintBottom_toBottomOf="@id/shutter" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/shutter" + app:layout_constraintTop_toTopOf="@id/shutter" + tools:src="@tools:sample/backgrounds/scenic" /> - - - - - - - - - + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:text="00:00" + tools:visibility="gone" /> - + From affcc078ddf2321333e8fc48599ab23452ac91e6 Mon Sep 17 00:00:00 2001 From: darthpaul Date: Thu, 18 Aug 2022 14:53:25 +0100 Subject: [PATCH 03/23] make preview fill up to the cutoff (notch) - add theme attribute android:windowLayoutInDisplayCutoutMode to shortEdges in the theme - add flags FLAG_LAYOUT_NO_LIMITS to the window flags - add top margin to the icons on top, set to the statusBarHeight - add bottom margin to the icons at the bottom, set to the navigationBarHeight + view margin --- .../camera/activities/MainActivity.kt | 19 +++++- app/src/main/res/layout/activity_main.xml | 60 +++++++++---------- app/src/main/res/values/styles.xml | 4 +- 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index 19660784..3a95791f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -64,16 +64,21 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera setShowWhenLocked(true) setTurnScreenOn(true) window.insetsController?.hide(WindowInsets.Type.statusBars()) + window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) } else if (isOreoMr1Plus()) { setShowWhenLocked(true) setTurnScreenOn(true) - window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + window.addFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN or + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + ) } else { window.addFlags( WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or - WindowManager.LayoutParams.FLAG_FULLSCREEN + WindowManager.LayoutParams.FLAG_FULLSCREEN or + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS ) } } @@ -214,6 +219,16 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera setContentView(R.layout.activity_main) initButtons() + (toggle_photo_video.layoutParams as ConstraintLayout.LayoutParams).setMargins( + 0, + statusBarHeight, + 0, + 0, + ) + + val goneMargin = (navigationBarHeight + resources.getDimension(R.dimen.big_margin)).toInt() + (shutter.layoutParams as ConstraintLayout.LayoutParams).goneBottomMargin = goneMargin + (video_rec_curr_timer.layoutParams as ConstraintLayout.LayoutParams).setMargins( 0, 0, diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 20cbc359..384dc4d3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -30,18 +30,31 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + - - - - + app:layout_constraintTop_toTopOf="@id/toggle_photo_video" /> + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b04b82ae..1ba2a099 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,11 +1,13 @@ - From 22df3d8c58a7446f099a1d946e6d42e718075f7d Mon Sep 17 00:00:00 2001 From: darthpaul Date: Fri, 19 Aug 2022 00:00:21 +0100 Subject: [PATCH 04/23] implement shutter animation - remove animation to blink icons - implement the shutter animation by setting alpha=1 and animating to alpha=0 --- .../camera/activities/MainActivity.kt | 10 ++-------- app/src/main/res/layout/activity_main.xml | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index 3a95791f..1cac8959 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -314,9 +314,8 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera if (mIsInPhotoMode) { toggleBottomButtons(true) mPreview?.tryTakePicture() - capture_black_screen.animate().alpha(0.8f).setDuration(CAPTURE_ANIMATION_DURATION).withEndAction { - capture_black_screen.animate().alpha(0f).setDuration(CAPTURE_ANIMATION_DURATION).start() - }.start() + shutter_animation.alpha = 1.0f + shutter_animation.animate().alpha(0f).setDuration(CAPTURE_ANIMATION_DURATION).start() } else { mPreview?.toggleRecording() } @@ -527,11 +526,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera override fun toggleBottomButtons(hide: Boolean) { runOnUiThread { - val alpha = if (hide) 0f else 1f - shutter.animate().alpha(alpha).start() - toggle_camera.animate().alpha(alpha).start() - toggle_flash.animate().alpha(alpha).start() - shutter.isClickable = !hide toggle_camera.isClickable = !hide toggle_flash.isClickable = !hide diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 384dc4d3..3914c758 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -14,7 +14,7 @@ android:layout_height="match_parent" /> Date: Fri, 19 Aug 2022 00:18:00 +0100 Subject: [PATCH 05/23] properly hide flash --- .../camera/activities/MainActivity.kt | 2 +- app/src/main/res/layout/activity_main.xml | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index 1cac8959..51208744 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -514,7 +514,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera if (available) { toggle_flash.beVisible() } else { - toggle_flash.beInvisible() + toggle_flash.beGone() toggle_flash.setImageResource(R.drawable.ic_flash_off_vector) mPreview?.setFlashlightState(FLASH_OFF) } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3914c758..27cc7530 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -35,11 +35,13 @@ android:id="@+id/toggle_photo_video" android:layout_width="@dimen/icon_size" android:layout_height="@dimen/icon_size" + android:layout_marginStart="@dimen/normal_margin" + android:layout_marginEnd="@dimen/normal_margin" android:contentDescription="@string/toggle_photo_video" android:padding="@dimen/normal_margin" android:src="@drawable/ic_video_vector" app:layout_constraintEnd_toStartOf="@id/toggle_flash" - app:layout_constraintHorizontal_chainStyle="spread" + app:layout_constraintHorizontal_chainStyle="spread_inside" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -47,6 +49,8 @@ android:id="@+id/toggle_flash" android:layout_width="@dimen/icon_size" android:layout_height="@dimen/icon_size" + android:layout_marginStart="@dimen/normal_margin" + android:layout_marginEnd="@dimen/normal_margin" android:layout_weight="1" android:contentDescription="@string/toggle_flash" android:padding="@dimen/normal_margin" @@ -62,6 +66,8 @@ android:layout_height="@dimen/icon_size" android:layout_below="@+id/toggle_photo_video" android:layout_alignParentEnd="true" + android:layout_marginStart="@dimen/normal_margin" + android:layout_marginEnd="@dimen/normal_margin" android:contentDescription="@string/resolution" android:padding="@dimen/normal_margin" android:src="@drawable/ic_resolution_vector" @@ -73,6 +79,8 @@ android:id="@+id/settings" android:layout_width="@dimen/icon_size" android:layout_height="@dimen/icon_size" + android:layout_marginStart="@dimen/normal_margin" + android:layout_marginEnd="@dimen/normal_margin" android:contentDescription="@string/settings" android:padding="@dimen/normal_margin" android:src="@drawable/ic_settings_cog_vector" From 2ebeb5a1e4d6e9488a8c34e372bbd4f8af621af2 Mon Sep 17 00:00:00 2001 From: darthpaul Date: Fri, 19 Aug 2022 15:24:47 +0100 Subject: [PATCH 06/23] use tabs for camera modes --- .../camera/activities/MainActivity.kt | 73 ++++++++++++++++--- .../camera/helpers/TabSelectedListener.kt | 9 +++ .../camera/implementations/CameraXPreview.kt | 23 +++++- .../implementations/CameraXPreviewListener.kt | 2 + app/src/main/res/drawable/tab_indicator.xml | 5 ++ .../res/drawable/tab_indicator_selected.xml | 14 ++++ .../res/drawable/tab_indicator_unselected.xml | 4 + app/src/main/res/layout/activity_main.xml | 61 ++++++++++------ app/src/main/res/values/dimens.xml | 1 + 9 files changed, 157 insertions(+), 35 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/camera/helpers/TabSelectedListener.kt create mode 100644 app/src/main/res/drawable/tab_indicator.xml create mode 100644 app/src/main/res/drawable/tab_indicator_selected.xml create mode 100644 app/src/main/res/drawable/tab_indicator_unselected.xml diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index 51208744..ac24fcce 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -15,6 +15,7 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestOptions +import com.google.android.material.tabs.TabLayout import com.simplemobiletools.camera.BuildConfig import com.simplemobiletools.camera.R import com.simplemobiletools.camera.extensions.config @@ -33,6 +34,8 @@ import kotlinx.android.synthetic.main.activity_main.* class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, CameraXPreviewListener { companion object { private const val CAPTURE_ANIMATION_DURATION = 100L + private const val PHOTO_MODE_INDEX = 1 + private const val VIDEO_MODE_INDEX = 0 } lateinit var mTimerHandler: Handler @@ -49,6 +52,12 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera private var mCurrVideoRecTimer = 0 var mLastHandledOrientation = 0 + private val tabSelectedListener = object : TabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + handleTogglePhotoVideo() + } + } + override fun onCreate(savedInstanceState: Bundle?) { useDynamicTheme = false super.onCreate(savedInstanceState) @@ -81,6 +90,32 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS ) } + + selectPhotoTab() + } + + private fun selectPhotoTab(triggerListener: Boolean = false) { + if (!triggerListener) { + removeTabListener() + } + camera_mode_tab.getTabAt(PHOTO_MODE_INDEX)?.select() + setTabListener() + } + + private fun selectVideoTab(triggerListener: Boolean = false) { + if (!triggerListener) { + removeTabListener() + } + camera_mode_tab.getTabAt(VIDEO_MODE_INDEX)?.select() + setTabListener() + } + + private fun setTabListener() { + camera_mode_tab.addOnTabSelectedListener(tabSelectedListener) + } + + private fun removeTabListener() { + camera_mode_tab.removeOnTabSelectedListener(tabSelectedListener) } override fun onResume() { @@ -158,9 +193,9 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } private fun hideIntentButtons() { - toggle_photo_video.beGone() + camera_mode_tab.beGone() settings.beGone() - last_photo_video_preview.beGone() + last_photo_video_preview.beInvisible() } private fun tryInitCamera() { @@ -193,6 +228,8 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } } + private fun is3rdPartyIntent() = isVideoCaptureIntent() || isImageCaptureIntent() + private fun isImageCaptureIntent(): Boolean = intent?.action == MediaStore.ACTION_IMAGE_CAPTURE || intent?.action == MediaStore.ACTION_IMAGE_CAPTURE_SECURE private fun isVideoCaptureIntent(): Boolean = intent?.action == MediaStore.ACTION_VIDEO_CAPTURE @@ -219,7 +256,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera setContentView(R.layout.activity_main) initButtons() - (toggle_photo_video.layoutParams as ConstraintLayout.LayoutParams).setMargins( + (toggle_flash.layoutParams as ConstraintLayout.LayoutParams).setMargins( 0, statusBarHeight, 0, @@ -237,8 +274,14 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera ) checkVideoCaptureIntent() + if (mIsInPhotoMode) { + selectPhotoTab() + } else { + selectVideoTab() + } + val outputUri = intent.extras?.get(MediaStore.EXTRA_OUTPUT) as? Uri - val is3rdPartyIntent = isVideoCaptureIntent() || isImageCaptureIntent() + val is3rdPartyIntent = is3rdPartyIntent() mPreview = CameraXInitializer(this).createCameraXPreview( preview_view, listener = this, @@ -271,7 +314,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera toggle_flash.setOnClickListener { toggleFlash() } shutter.setOnClickListener { shutterPressed() } settings.setOnClickListener { launchSettings() } - toggle_photo_video.setOnClickListener { handleTogglePhotoVideo() } change_resolution.setOnClickListener { mPreview?.showChangeResolutionDialog() } } @@ -332,6 +374,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera togglePhotoVideo() } else { toast(R.string.no_audio_permissions) + selectPhotoTab() if (isVideoCaptureIntent()) { finish() } @@ -369,10 +412,10 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } private fun initPhotoMode() { - toggle_photo_video.setImageResource(R.drawable.ic_video_vector) shutter.setImageResource(R.drawable.ic_shutter_vector) mPreview?.initPhotoMode() setupPreviewImage(true) + selectPhotoTab() } private fun tryInitVideoMode() { @@ -387,10 +430,10 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } private fun initVideoButtons() { - toggle_photo_video.setImageResource(R.drawable.ic_camera_vector) shutter.setImageResource(R.drawable.ic_video_rec) setupPreviewImage(false) mPreview?.checkFlashlight() + selectVideoTab() } private fun setupPreviewImage(isPhoto: Boolean) { @@ -487,7 +530,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } private fun animateViews(degrees: Int) { - val views = arrayOf(toggle_camera, toggle_flash, toggle_photo_video, change_resolution, shutter, settings, last_photo_video_preview) + val views = arrayOf(toggle_camera, toggle_flash, change_resolution, shutter, settings, last_photo_video_preview) for (view in views) { rotate(view, degrees) } @@ -514,7 +557,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera if (available) { toggle_flash.beVisible() } else { - toggle_flash.beGone() + toggle_flash.beInvisible() toggle_flash.setImageResource(R.drawable.ic_flash_off_vector) mPreview?.setFlashlightState(FLASH_OFF) } @@ -587,6 +630,18 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera mFocusCircleView.drawFocusCircle(xPos, yPos) } + override fun onSwipeLeft() { + if (!is3rdPartyIntent()) { + selectPhotoTab(triggerListener = true) + } + } + + override fun onSwipeRight() { + if (!is3rdPartyIntent()) { + selectVideoTab(triggerListener = true) + } + } + fun setRecordingState(isRecording: Boolean) { runOnUiThread { if (isRecording) { diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/TabSelectedListener.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/TabSelectedListener.kt new file mode 100644 index 00000000..361729ec --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/TabSelectedListener.kt @@ -0,0 +1,9 @@ +package com.simplemobiletools.camera.helpers + +import com.google.android.material.tabs.TabLayout + +interface TabSelectedListener : TabLayout.OnTabSelectedListener { + override fun onTabReselected(tab: TabLayout.Tab?){} + + override fun onTabUnselected(tab: TabLayout.Tab?) {} +} 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 4b7ecbbc..1260e0d0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt @@ -31,6 +31,7 @@ import com.simplemobiletools.camera.models.MediaOutput import com.simplemobiletools.camera.models.MySize import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import kotlin.math.abs class CameraXPreview( private val activity: AppCompatActivity, @@ -45,6 +46,7 @@ class CameraXPreview( // Auto focus is 1/6 of the area. private const val AF_SIZE = 1.0f / 6.0f private const val AE_SIZE = AF_SIZE * 1.5f + private const val MIN_SWIPE_DISTANCE_X = 100 } private val config = activity.config @@ -278,11 +280,26 @@ class CameraXPreview( true } ?: false } + + override fun onFling(event1: MotionEvent, event2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { + val deltaX = event1.x - event2.x + val deltaXAbs = abs(deltaX) + + if (deltaXAbs >= MIN_SWIPE_DISTANCE_X) { + if (deltaX > 0) { + listener.onSwipeLeft() + } else { + listener.onSwipeRight() + } + } + + return true + } }) previewView.setOnTouchListener { _, event -> - gestureDetector.onTouchEvent(event) - scaleGesture?.onTouchEvent(event) - true + val handledGesture = gestureDetector.onTouchEvent(event) + val handledScaleGesture = scaleGesture?.onTouchEvent(event) + handledGesture || handledScaleGesture ?: false } } diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt index 73bf5db5..85657e43 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt @@ -16,4 +16,6 @@ interface CameraXPreviewListener { fun onVideoRecordingStopped() fun onVideoDurationChanged(durationNanos: Long) fun onFocusCamera(xPos: Float, yPos: Float) + fun onSwipeLeft() + fun onSwipeRight() } diff --git a/app/src/main/res/drawable/tab_indicator.xml b/app/src/main/res/drawable/tab_indicator.xml new file mode 100644 index 00000000..925ef230 --- /dev/null +++ b/app/src/main/res/drawable/tab_indicator.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/tab_indicator_selected.xml b/app/src/main/res/drawable/tab_indicator_selected.xml new file mode 100644 index 00000000..e9b77dcf --- /dev/null +++ b/app/src/main/res/drawable/tab_indicator_selected.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/tab_indicator_unselected.xml b/app/src/main/res/drawable/tab_indicator_unselected.xml new file mode 100644 index 00000000..76589c0c --- /dev/null +++ b/app/src/main/res/drawable/tab_indicator_unselected.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 27cc7530..3ab53b46 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -30,21 +30,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - - + app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="@id/toggle_flash" /> + app:layout_constraintTop_toTopOf="@id/toggle_flash" /> - + app:constraint_referenced_ids="settings,change_resolution,toggle_flash" /> + app:layout_constraintTop_toTopOf="@id/camera_mode_tab" /> + + + + + + + + + 56dp + 10dp From e8744c93f6083a908b28506597d0a6899e065d8d Mon Sep 17 00:00:00 2001 From: darthpaul Date: Fri, 19 Aug 2022 16:27:16 +0100 Subject: [PATCH 07/23] remove keepSettingsVisible --- .../camera/activities/SettingsActivity.kt | 13 ------------- .../simplemobiletools/camera/helpers/Config.kt | 5 ----- app/src/main/res/layout/activity_settings.xml | 15 --------------- 3 files changed, 33 deletions(-) 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 5b7596e9..55fe06c8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/SettingsActivity.kt @@ -34,7 +34,6 @@ class SettingsActivity : SimpleActivity() { setupSound() setupVolumeButtonsAsShutter() setupFlipPhotos() - setupKeepSettingsVisible() setupSavePhotoMetadata() setupSavePhotosFolder() setupPhotoQuality() @@ -99,10 +98,6 @@ class SettingsActivity : SimpleActivity() { settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en") settings_use_english.isChecked = config.useEnglish - if (settings_use_english_holder.isGone() && settings_purchase_thank_you_holder.isGone()) { - settings_keep_settings_visible_holder.background = ResourcesCompat.getDrawable(resources, R.drawable.ripple_all_corners, theme) - } - settings_use_english_holder.setOnClickListener { settings_use_english.toggle() config.useEnglish = settings_use_english.isChecked @@ -154,14 +149,6 @@ class SettingsActivity : SimpleActivity() { } } - private fun setupKeepSettingsVisible() { - settings_keep_settings_visible.isChecked = config.keepSettingsVisible - settings_keep_settings_visible_holder.setOnClickListener { - settings_keep_settings_visible.toggle() - config.keepSettingsVisible = settings_keep_settings_visible.isChecked - } - } - private fun setupSavePhotoMetadata() { settings_save_photo_metadata.isChecked = config.savePhotoMetadata settings_save_photo_metadata_holder.setOnClickListener { 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 567d3af1..ec229691 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt @@ -66,11 +66,6 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getInt(FRONT_VIDEO_RESOLUTION_INDEX, 0) set(frontVideoResIndex) = prefs.edit().putInt(FRONT_VIDEO_RESOLUTION_INDEX, frontVideoResIndex).apply() - //TODO: Remove keepSettingsVisible since the view has moved to the top - var keepSettingsVisible: Boolean - get() = prefs.getBoolean(KEEP_SETTINGS_VISIBLE, false) - set(keepSettingsVisible) = prefs.edit().putBoolean(KEEP_SETTINGS_VISIBLE, keepSettingsVisible).apply() - var savePhotoMetadata: Boolean get() = prefs.getBoolean(SAVE_PHOTO_METADATA, true) set(savePhotoMetadata) = prefs.edit().putBoolean(SAVE_PHOTO_METADATA, savePhotoMetadata).apply() diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index ef74273d..3353645e 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -116,21 +116,6 @@ - - - - - Date: Mon, 22 Aug 2022 14:27:07 +0100 Subject: [PATCH 08/23] simpler UI for flash mode / media size --- .../camera/activities/MainActivity.kt | 141 +++++++++++++++++- .../camera/extensions/Int.kt | 20 ++- .../camera/helpers/ImageQualityManager.kt | 2 +- .../camera/implementations/CameraXPreview.kt | 60 +++++--- .../implementations/CameraXPreviewListener.kt | 15 +- .../camera/interfaces/MyPreview.kt | 4 +- .../simplemobiletools/camera/models/MySize.kt | 24 +++ .../camera/models/ResolutionOption.kt | 9 ++ .../camera/models/VideoQuality.kt | 24 ++- .../main/res/color/camera_option_color.xml | 6 + app/src/main/res/color/tab_color.xml | 5 + .../main/res/drawable/gradient_background.xml | 7 - .../drawable/gradient_background_flipped.xml | 7 - .../res/drawable/ic_flash_auto_vector.xml | 2 +- .../main/res/drawable/ic_flash_off_vector.xml | 2 +- .../main/res/drawable/ic_flash_on_vector.xml | 2 +- app/src/main/res/drawable/ic_photo_16x9.xml | 31 ++++ app/src/main/res/drawable/ic_photo_1x1.xml | 26 ++++ app/src/main/res/drawable/ic_photo_4x3.xml | 26 ++++ app/src/main/res/drawable/ic_photo_full.xml | 9 ++ app/src/main/res/drawable/ic_video_fhd.xml | 19 +++ app/src/main/res/drawable/ic_video_hd.xml | 17 +++ app/src/main/res/drawable/ic_video_sd.xml | 17 +++ app/src/main/res/drawable/ic_video_uhd.xml | 20 +++ app/src/main/res/drawable/tab_indicator.xml | 17 ++- .../res/drawable/tab_indicator_selected.xml | 14 -- .../res/drawable/tab_indicator_unselected.xml | 4 - app/src/main/res/layout/activity_main.xml | 49 ++++-- app/src/main/res/layout/layout_button.xml | 8 + app/src/main/res/layout/layout_flash.xml | 36 +++++ app/src/main/res/layout/layout_media_size.xml | 8 + app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/ids.xml | 11 ++ app/src/main/res/values/styles.xml | 14 ++ 34 files changed, 574 insertions(+), 84 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/camera/models/ResolutionOption.kt create mode 100644 app/src/main/res/color/camera_option_color.xml create mode 100644 app/src/main/res/color/tab_color.xml delete mode 100644 app/src/main/res/drawable/gradient_background.xml delete mode 100644 app/src/main/res/drawable/gradient_background_flipped.xml create mode 100644 app/src/main/res/drawable/ic_photo_16x9.xml create mode 100644 app/src/main/res/drawable/ic_photo_1x1.xml create mode 100644 app/src/main/res/drawable/ic_photo_4x3.xml create mode 100644 app/src/main/res/drawable/ic_photo_full.xml create mode 100644 app/src/main/res/drawable/ic_video_fhd.xml create mode 100644 app/src/main/res/drawable/ic_video_hd.xml create mode 100644 app/src/main/res/drawable/ic_video_sd.xml create mode 100644 app/src/main/res/drawable/ic_video_uhd.xml delete mode 100644 app/src/main/res/drawable/tab_indicator_selected.xml delete mode 100644 app/src/main/res/drawable/tab_indicator_unselected.xml create mode 100644 app/src/main/res/layout/layout_button.xml create mode 100644 app/src/main/res/layout/layout_flash.xml create mode 100644 app/src/main/res/layout/layout_media_size.xml create mode 100644 app/src/main/res/values/ids.xml diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index ac24fcce..66922bc7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -10,30 +10,41 @@ import android.os.Handler import android.os.Looper import android.provider.MediaStore import android.view.* +import android.widget.LinearLayout +import androidx.appcompat.content.res.AppCompatResources import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.WindowCompat import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestOptions +import com.google.android.material.button.MaterialButton +import com.google.android.material.button.MaterialButtonToggleGroup import com.google.android.material.tabs.TabLayout import com.simplemobiletools.camera.BuildConfig import com.simplemobiletools.camera.R import com.simplemobiletools.camera.extensions.config +import com.simplemobiletools.camera.extensions.idToAppFlashMode +import com.simplemobiletools.camera.extensions.toFlashModeId import com.simplemobiletools.camera.helpers.* import com.simplemobiletools.camera.implementations.CameraXInitializer import com.simplemobiletools.camera.implementations.CameraXPreviewListener import com.simplemobiletools.camera.implementations.MyCameraImpl import com.simplemobiletools.camera.interfaces.MyPreview +import com.simplemobiletools.camera.models.ResolutionOption import com.simplemobiletools.camera.views.FocusCircleView import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.models.Release import java.util.concurrent.TimeUnit import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.layout_flash.flash_auto +import kotlinx.android.synthetic.main.layout_flash.flash_toggle_group +import kotlinx.android.synthetic.main.layout_media_size.media_size_toggle_group class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, CameraXPreviewListener { companion object { - private const val CAPTURE_ANIMATION_DURATION = 100L + private const val CAPTURE_ANIMATION_DURATION = 500L private const val PHOTO_MODE_INDEX = 1 private const val VIDEO_MODE_INDEX = 0 } @@ -43,9 +54,9 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera private lateinit var mFocusCircleView: FocusCircleView private lateinit var mFadeHandler: Handler private lateinit var mCameraImpl: MyCameraImpl - private var mPreview: MyPreview? = null private var mPreviewUri: Uri? = null + private var buttonCheckedListener: MaterialButtonToggleGroup.OnButtonCheckedListener? = null private var mIsInPhotoMode = true private var mIsCameraAvailable = false private var mIsHardwareShutterHandled = false @@ -63,6 +74,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera super.onCreate(savedInstanceState) appLaunched(BuildConfig.APPLICATION_ID) requestWindowFeature(Window.FEATURE_NO_TITLE) + WindowCompat.setDecorFitsSystemWindows(window, false) initVariables() tryInitCamera() @@ -155,6 +167,12 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera mPreview = null } + override fun onBackPressed() { + if (!closeOptions()) { + super.onBackPressed() + } + } + private fun initVariables() { mIsInPhotoMode = if (isVideoCaptureIntent()) { false @@ -263,14 +281,28 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera 0, ) - val goneMargin = (navigationBarHeight + resources.getDimension(R.dimen.big_margin)).toInt() + (flash_toggle_group.layoutParams as ConstraintLayout.LayoutParams).setMargins( + 0, + statusBarHeight, + 0, + 0, + ) + + (media_size_toggle_group.layoutParams as ConstraintLayout.LayoutParams).setMargins( + 0, + statusBarHeight, + 0, + 0, + ) + + val goneMargin = (navigationBarHeight + resources.getDimension(R.dimen.bigger_margin)).toInt() (shutter.layoutParams as ConstraintLayout.LayoutParams).goneBottomMargin = goneMargin (video_rec_curr_timer.layoutParams as ConstraintLayout.LayoutParams).setMargins( 0, 0, 0, - (navigationBarHeight + resources.getDimension(R.dimen.big_margin)).toInt() + (navigationBarHeight + resources.getDimension(R.dimen.bigger_margin)).toInt() ) checkVideoCaptureIntent() @@ -314,7 +346,15 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera toggle_flash.setOnClickListener { toggleFlash() } shutter.setOnClickListener { shutterPressed() } settings.setOnClickListener { launchSettings() } - change_resolution.setOnClickListener { mPreview?.showChangeResolutionDialog() } + change_resolution.setOnClickListener { mPreview?.showChangeResolution() } + flash_toggle_group.check(config.flashlightState.toFlashModeId()) + flash_toggle_group.addOnButtonCheckedListener { _, checkedId, isChecked -> + if (isChecked) { + val flashMode = checkedId.idToAppFlashMode() + closeOptions() + mPreview?.setFlashlightState(flashMode) + } + } } private fun toggleCamera() { @@ -332,7 +372,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera private fun toggleFlash() { if (checkCameraAvailable()) { - mPreview?.toggleFlashlight() + showFlashOptions(mIsInPhotoMode) } } @@ -642,6 +682,95 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } } + override fun onTouchPreview() { + closeOptions() + } + + private fun closeOptions(): Boolean { + if (media_size_toggle_group.isVisible()) { + media_size_toggle_group.beGone() + top_group.beVisible() + return true + } + + if (flash_toggle_group.isVisible()) { + flash_toggle_group.beGone() + top_group.beVisible() + return true + } + return false + } + + override fun displaySelectedResolution(resolutionOption: ResolutionOption) { + val imageRes = resolutionOption.imageDrawableResId + change_resolution.setImageResource(imageRes) + } + + override fun showImageSizes( + selectedResolution: ResolutionOption, + resolutions: List, + isPhotoCapture: Boolean, + isFrontCamera: Boolean, + onSelect: (changed: Boolean) -> Unit + ) { + + media_size_toggle_group.removeAllViews() + media_size_toggle_group.clearChecked() + + resolutions.map(::createButton).forEach { button -> + media_size_toggle_group.addView(button) + } + + buttonCheckedListener?.let { media_size_toggle_group.removeOnButtonCheckedListener(it) } + buttonCheckedListener = MaterialButtonToggleGroup.OnButtonCheckedListener { _, checkedId, isChecked -> + if (isChecked) { + val index = resolutions.indexOfFirst { it.buttonViewId == checkedId } + if (isPhotoCapture) { + if (isFrontCamera) { + config.frontPhotoResIndex = index + } else { + config.backPhotoResIndex = index + } + } else { + if (isFrontCamera) { + config.frontVideoResIndex = index + } else { + config.backVideoResIndex = index + } + } + closeOptions() + onSelect.invoke(selectedResolution.buttonViewId != checkedId) + } + } + buttonCheckedListener?.let { + media_size_toggle_group.check(selectedResolution.buttonViewId) + media_size_toggle_group.addOnButtonCheckedListener(it) + } + showResolutionOptions() + } + + private fun createButton(resolutionOption: ResolutionOption): MaterialButton { + val params = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT).apply { + weight = 1f + } + return (layoutInflater.inflate(R.layout.layout_button, null) as MaterialButton).apply { + layoutParams = params + icon = AppCompatResources.getDrawable(context, resolutionOption.imageDrawableResId) + id = resolutionOption.buttonViewId + } + } + + private fun showResolutionOptions() { + top_group.beInvisible() + media_size_toggle_group.beVisible() + } + + override fun showFlashOptions(photoCapture: Boolean) { + flash_auto.beVisibleIf(photoCapture) + top_group.beInvisible() + flash_toggle_group.beVisible() + } + fun setRecordingState(isRecording: Boolean) { runOnUiThread { if (isRecording) { diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/extensions/Int.kt b/app/src/main/kotlin/com/simplemobiletools/camera/extensions/Int.kt index 4cf4caf7..04794b43 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/extensions/Int.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/extensions/Int.kt @@ -2,10 +2,10 @@ package com.simplemobiletools.camera.extensions import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCapture +import com.simplemobiletools.camera.R import com.simplemobiletools.camera.helpers.FLASH_AUTO import com.simplemobiletools.camera.helpers.FLASH_OFF import com.simplemobiletools.camera.helpers.FLASH_ON -import java.lang.IllegalArgumentException fun Int.toCameraXFlashMode(): Int { return when (this) { @@ -25,6 +25,24 @@ fun Int.toAppFlashMode(): Int { } } +fun Int.toFlashModeId(): Int { + return when (this) { + FLASH_ON -> R.id.flash_on + FLASH_OFF -> R.id.flash_on + FLASH_AUTO -> R.id.flash_auto + else -> throw IllegalArgumentException("Unknown mode: $this") + } +} + +fun Int.idToAppFlashMode(): Int { + return when (this) { + R.id.flash_on -> FLASH_ON + R.id.flash_off -> FLASH_OFF + R.id.flash_auto -> FLASH_AUTO + else -> throw IllegalArgumentException("Unknown mode: $this") + } +} + fun Int.toCameraSelector(): CameraSelector { return if (this == CameraSelector.LENS_FACING_FRONT) { CameraSelector.DEFAULT_FRONT_CAMERA 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 741be610..5e5d1c9a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ImageQualityManager.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ImageQualityManager.kt @@ -53,7 +53,7 @@ class ImageQualityManager( fun getUserSelectedResolution(cameraSelector: CameraSelector): MySize { val resolutions = getSupportedResolutions(cameraSelector) var index = if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) config.frontPhotoResIndex else config.backPhotoResIndex - index = index.coerceAtMost(resolutions.lastIndex) + index = index.coerceAtMost(resolutions.lastIndex).coerceAtLeast(0) return resolutions[index] } 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 1260e0d0..20c4b83f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt @@ -29,6 +29,7 @@ import com.simplemobiletools.camera.helpers.* import com.simplemobiletools.camera.interfaces.MyPreview import com.simplemobiletools.camera.models.MediaOutput import com.simplemobiletools.camera.models.MySize +import com.simplemobiletools.camera.models.ResolutionOption import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.helpers.ensureBackgroundThread import kotlin.math.abs @@ -128,9 +129,13 @@ class CameraXPreview( val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.") val resolution = if (isPhotoCapture) { - imageQualityManager.getUserSelectedResolution(cameraSelector) + imageQualityManager.getUserSelectedResolution(cameraSelector).also { + displaySelectedResolution(it.toResolutionOption()) + } } else { - val selectedQuality = videoQualityManager.getUserSelectedQuality(cameraSelector) + val selectedQuality = videoQualityManager.getUserSelectedQuality(cameraSelector).also { + displaySelectedResolution(it.toResolutionOption()) + } MySize(selectedQuality.width, selectedQuality.height) } @@ -174,6 +179,10 @@ class CameraXPreview( setupZoomAndFocus() } + private fun displaySelectedResolution(resolutionOption: ResolutionOption) { + listener.displaySelectedResolution(resolutionOption) + } + private fun getRotatedResolution(resolution: MySize, rotationDegrees: Int): Size { return if (rotationDegrees == Surface.ROTATION_0 || rotationDegrees == Surface.ROTATION_180) { Size(resolution.height, resolution.width) @@ -261,6 +270,11 @@ class CameraXPreview( private fun setupZoomAndFocus() { val scaleGesture = camera?.let { ScaleGestureDetector(activity, PinchToZoomOnScaleGestureListener(it.cameraInfo, it.cameraControl)) } val gestureDetector = GestureDetector(activity, object : SimpleOnGestureListener() { + override fun onDown(e: MotionEvent?): Boolean { + listener.onTouchPreview() + return super.onDown(e) + } + override fun onSingleTapConfirmed(event: MotionEvent): Boolean { return camera?.cameraInfo?.let { val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY) @@ -326,6 +340,28 @@ class CameraXPreview( } } + override fun showChangeResolution() { + val imageSizes = imageQualityManager.getSupportedResolutions(cameraSelector) + val videoSizes = videoQualityManager.getSupportedQualities(cameraSelector) + val selectedVideoSize = videoQualityManager.getUserSelectedQuality(cameraSelector) + val selectedImageSize = imageQualityManager.getUserSelectedResolution(cameraSelector) + + val selectedResolution = if (isPhotoCapture) selectedImageSize.toResolutionOption() else selectedVideoSize.toResolutionOption() + val resolutions = if (isPhotoCapture) imageSizes.map { it.toResolutionOption() } else videoSizes.map { it.toResolutionOption() } + + listener.showImageSizes( + selectedResolution = selectedResolution, + resolutions = resolutions, + isPhotoCapture = isPhotoCapture, + isFrontCamera = isFrontCameraInUse() + ) { changed -> + if (changed) { + currentRecording?.stop() + } + startCamera() + } + } + override fun toggleFrontBackCamera() { val newCameraSelector = if (isFrontCameraInUse()) { CameraSelector.DEFAULT_BACK_CAMERA @@ -337,22 +373,10 @@ class CameraXPreview( startCamera(switching = true) } - override fun toggleFlashlight() { - val newFlashMode = if (isPhotoCapture) { - when (flashMode) { - FLASH_MODE_OFF -> FLASH_MODE_ON - FLASH_MODE_ON -> FLASH_MODE_AUTO - FLASH_MODE_AUTO -> FLASH_MODE_OFF - else -> throw IllegalArgumentException("Unknown mode: $flashMode") - } - } else { - when (flashMode) { - FLASH_MODE_OFF -> FLASH_MODE_ON - FLASH_MODE_ON -> FLASH_MODE_OFF - else -> throw IllegalArgumentException("Unknown mode: $flashMode") - }.also { - camera?.cameraControl?.enableTorch(it == FLASH_MODE_ON) - } + override fun setFlashlightState(state: Int) { + val newFlashMode = state.toCameraXFlashMode() + if (!isPhotoCapture) { + camera?.cameraControl?.enableTorch(newFlashMode == FLASH_MODE_ON) } flashMode = newFlashMode imageCapture?.flashMode = newFlashMode diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt index 85657e43..69d8cc3e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt @@ -2,13 +2,14 @@ package com.simplemobiletools.camera.implementations import android.graphics.Bitmap import android.net.Uri +import com.simplemobiletools.camera.models.ResolutionOption interface CameraXPreviewListener { fun setCameraAvailable(available: Boolean) - fun setHasFrontAndBackCamera(hasFrontAndBack:Boolean) + fun setHasFrontAndBackCamera(hasFrontAndBack: Boolean) fun setFlashAvailable(available: Boolean) fun onChangeCamera(frontCamera: Boolean) - fun toggleBottomButtons(hide:Boolean) + fun toggleBottomButtons(hide: Boolean) fun onMediaSaved(uri: Uri) fun onImageCaptured(bitmap: Bitmap) fun onChangeFlashMode(flashMode: Int) @@ -18,4 +19,14 @@ interface CameraXPreviewListener { fun onFocusCamera(xPos: Float, yPos: Float) fun onSwipeLeft() fun onSwipeRight() + fun onTouchPreview() + fun displaySelectedResolution(resolutionOption: ResolutionOption) + fun showImageSizes( + selectedResolution: ResolutionOption, + resolutions: List, + isPhotoCapture: Boolean, + isFrontCamera: Boolean, + onSelect: (changed:Boolean) -> Unit, + ) + fun showFlashOptions(photoCapture: Boolean) } diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt b/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt index 81125acb..cc670165 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt @@ -20,7 +20,7 @@ interface MyPreview { fun toggleFrontBackCamera() - fun toggleFlashlight() + fun toggleFlashlight() = Unit fun tryTakePicture() @@ -31,4 +31,6 @@ interface MyPreview { fun initVideoMode() fun checkFlashlight() = Unit + + fun showChangeResolution() = Unit } 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 3ad93eac..158c91d0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/models/MySize.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/models/MySize.kt @@ -2,6 +2,8 @@ package com.simplemobiletools.camera.models import android.content.Context import android.util.Size +import androidx.annotation.DrawableRes +import androidx.annotation.IdRes import com.simplemobiletools.camera.R data class MySize(val width: Int, val height: Int, val isFullScreen: Boolean = false) { @@ -57,5 +59,27 @@ data class MySize(val width: Int, val height: Int, val isFullScreen: Boolean = f else -> context.resources.getString(R.string.other) } + @DrawableRes + fun getImageResId(): Int = when { + isFullScreen -> R.drawable.ic_photo_full + isSixteenToNine() -> R.drawable.ic_photo_16x9 + isFourToThree() -> R.drawable.ic_photo_4x3 + isSquare() -> R.drawable.ic_photo_1x1 + else -> throw UnsupportedOperationException("This size $this is not supported") + } + + @IdRes + fun getButtonId(): Int = when { + isFullScreen -> R.id.photo_full + isSixteenToNine() -> R.id.photo_16x9 + isFourToThree() -> R.id.photo_4x3 + isSquare() -> R.id.photo_1x1 + else -> throw UnsupportedOperationException("This size $this is not supported") + } + + fun toResolutionOption(): ResolutionOption { + return ResolutionOption(buttonViewId = getButtonId(), imageDrawableResId = getImageResId()) + } + fun toSize() = Size(width, height) } diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/models/ResolutionOption.kt b/app/src/main/kotlin/com/simplemobiletools/camera/models/ResolutionOption.kt new file mode 100644 index 00000000..39b9eac6 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/camera/models/ResolutionOption.kt @@ -0,0 +1,9 @@ +package com.simplemobiletools.camera.models + +import androidx.annotation.DrawableRes +import androidx.annotation.IdRes + +data class ResolutionOption( + @IdRes val buttonViewId: Int, + @DrawableRes val imageDrawableResId: Int, +) diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/models/VideoQuality.kt b/app/src/main/kotlin/com/simplemobiletools/camera/models/VideoQuality.kt index ec2a536b..adcfefbd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/models/VideoQuality.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/models/VideoQuality.kt @@ -1,6 +1,8 @@ package com.simplemobiletools.camera.models import android.content.Context +import androidx.annotation.DrawableRes +import androidx.annotation.IdRes import com.simplemobiletools.camera.R enum class VideoQuality(val width: Int, val height: Int) { @@ -11,7 +13,7 @@ enum class VideoQuality(val width: Int, val height: Int) { val pixels: Int = width * height - val megaPixels: String = String.format("%.1f", (width * height.toFloat()) / VideoQuality.ONE_MEGA_PIXEL) + val megaPixels: String = String.format("%.1f", (width * height.toFloat()) / VideoQuality.ONE_MEGA_PIXEL) val ratio = width / height.toFloat() @@ -52,6 +54,26 @@ enum class VideoQuality(val width: Int, val height: Int) { else -> context.resources.getString(R.string.other) } + @DrawableRes + fun getImageResId(): Int = when (this) { + UHD -> R.drawable.ic_video_uhd + FHD -> R.drawable.ic_video_fhd + HD -> R.drawable.ic_video_hd + SD -> R.drawable.ic_video_sd + } + + @IdRes + fun getButtonId(): Int = when (this) { + UHD -> R.id.video_uhd + FHD -> R.id.video_fhd + HD -> R.id.video_hd + SD -> R.id.video_sd + } + + fun toResolutionOption(): ResolutionOption { + return ResolutionOption(buttonViewId = getButtonId(), imageDrawableResId = getImageResId()) + } + companion object { private const val ONE_MEGA_PIXEL = 1000000 } diff --git a/app/src/main/res/color/camera_option_color.xml b/app/src/main/res/color/camera_option_color.xml new file mode 100644 index 00000000..d4349ec0 --- /dev/null +++ b/app/src/main/res/color/camera_option_color.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/color/tab_color.xml b/app/src/main/res/color/tab_color.xml new file mode 100644 index 00000000..02947e5a --- /dev/null +++ b/app/src/main/res/color/tab_color.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/gradient_background.xml b/app/src/main/res/drawable/gradient_background.xml deleted file mode 100644 index 68f8cc81..00000000 --- a/app/src/main/res/drawable/gradient_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/gradient_background_flipped.xml b/app/src/main/res/drawable/gradient_background_flipped.xml deleted file mode 100644 index e8481212..00000000 --- a/app/src/main/res/drawable/gradient_background_flipped.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_flash_auto_vector.xml b/app/src/main/res/drawable/ic_flash_auto_vector.xml index 308f724c..bdd3aab7 100644 --- a/app/src/main/res/drawable/ic_flash_auto_vector.xml +++ b/app/src/main/res/drawable/ic_flash_auto_vector.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_flash_off_vector.xml b/app/src/main/res/drawable/ic_flash_off_vector.xml index 9d4b374c..1e019ab3 100644 --- a/app/src/main/res/drawable/ic_flash_off_vector.xml +++ b/app/src/main/res/drawable/ic_flash_off_vector.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_flash_on_vector.xml b/app/src/main/res/drawable/ic_flash_on_vector.xml index 57cd3a63..6947cbab 100644 --- a/app/src/main/res/drawable/ic_flash_on_vector.xml +++ b/app/src/main/res/drawable/ic_flash_on_vector.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_photo_16x9.xml b/app/src/main/res/drawable/ic_photo_16x9.xml new file mode 100644 index 00000000..bc210589 --- /dev/null +++ b/app/src/main/res/drawable/ic_photo_16x9.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_photo_1x1.xml b/app/src/main/res/drawable/ic_photo_1x1.xml new file mode 100644 index 00000000..3d9a453f --- /dev/null +++ b/app/src/main/res/drawable/ic_photo_1x1.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_photo_4x3.xml b/app/src/main/res/drawable/ic_photo_4x3.xml new file mode 100644 index 00000000..2907e907 --- /dev/null +++ b/app/src/main/res/drawable/ic_photo_4x3.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_photo_full.xml b/app/src/main/res/drawable/ic_photo_full.xml new file mode 100644 index 00000000..48697239 --- /dev/null +++ b/app/src/main/res/drawable/ic_photo_full.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_video_fhd.xml b/app/src/main/res/drawable/ic_video_fhd.xml new file mode 100644 index 00000000..16b240a5 --- /dev/null +++ b/app/src/main/res/drawable/ic_video_fhd.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_video_hd.xml b/app/src/main/res/drawable/ic_video_hd.xml new file mode 100644 index 00000000..efc8d803 --- /dev/null +++ b/app/src/main/res/drawable/ic_video_hd.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_video_sd.xml b/app/src/main/res/drawable/ic_video_sd.xml new file mode 100644 index 00000000..1db2f721 --- /dev/null +++ b/app/src/main/res/drawable/ic_video_sd.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_video_uhd.xml b/app/src/main/res/drawable/ic_video_uhd.xml new file mode 100644 index 00000000..3aa0d9d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_video_uhd.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/app/src/main/res/drawable/tab_indicator.xml b/app/src/main/res/drawable/tab_indicator.xml index 925ef230..afaa1853 100644 --- a/app/src/main/res/drawable/tab_indicator.xml +++ b/app/src/main/res/drawable/tab_indicator.xml @@ -1,5 +1,14 @@ - - - - + + + + + + + + + diff --git a/app/src/main/res/drawable/tab_indicator_selected.xml b/app/src/main/res/drawable/tab_indicator_selected.xml deleted file mode 100644 index e9b77dcf..00000000 --- a/app/src/main/res/drawable/tab_indicator_selected.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/tab_indicator_unselected.xml b/app/src/main/res/drawable/tab_indicator_unselected.xml deleted file mode 100644 index 76589c0c..00000000 --- a/app/src/main/res/drawable/tab_indicator_unselected.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3ab53b46..53ab12f8 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" - android:fitsSystemWindows="true"> + android:fitsSystemWindows="false"> - + app:layout_constraintTop_toTopOf="parent" + tools:visibility="visible" /> + + + + + app:layout_constraintTop_toTopOf="@id/toggle_flash" + tools:src="@drawable/ic_photo_4x3" /> + + diff --git a/app/src/main/res/layout/layout_flash.xml b/app/src/main/res/layout/layout_flash.xml new file mode 100644 index 00000000..fd3bdfba --- /dev/null +++ b/app/src/main/res/layout/layout_flash.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_media_size.xml b/app/src/main/res/layout/layout_media_size.xml new file mode 100644 index 00000000..c7562f84 --- /dev/null +++ b/app/src/main/res/layout/layout_media_size.xml @@ -0,0 +1,8 @@ + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 5ff71dc3..5331afbe 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,5 +1,7 @@ 56dp + 48dp + 24dp 10dp diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml new file mode 100644 index 00000000..020243de --- /dev/null +++ b/app/src/main/res/values/ids.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 1ba2a099..4b6004f7 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -4,10 +4,24 @@ + + From c220b478c08a557772238f02324096e6e8edb9ba Mon Sep 17 00:00:00 2001 From: darthpaul Date: Thu, 25 Aug 2022 06:56:15 +0100 Subject: [PATCH 09/23] UI fixes - handle top/button margin if there's a cutout with windowInsets.displayCutout - handle nav bar color - remove 16:9 size if the full screen size is already 16:9 - change layout to use Button instead of explicit MaterialButton --- .../camera/activities/MainActivity.kt | 79 ++++++++----------- .../camera/helpers/ImageQualityManager.kt | 4 +- .../simplemobiletools/camera/models/MySize.kt | 10 ++- app/src/main/res/layout/activity_main.xml | 9 +-- app/src/main/res/layout/layout_button.xml | 2 +- app/src/main/res/layout/layout_flash.xml | 6 +- app/src/main/res/values/colors.xml | 2 +- app/src/main/res/values/styles.xml | 6 +- 8 files changed, 53 insertions(+), 65 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index 66922bc7..dbe50af4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -13,7 +13,8 @@ import android.view.* import android.widget.LinearLayout import androidx.appcompat.content.res.AppCompatResources import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.view.WindowCompat +import androidx.core.content.ContextCompat +import androidx.core.view.* import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions @@ -81,29 +82,23 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera supportActionBar?.hide() checkWhatsNewDialog() setupOrientationEventListener() - if (isRPlus()) { + + val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView) + windowInsetsController?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + windowInsetsController?.hide(WindowInsetsCompat.Type.statusBars()) + + if (isOreoMr1Plus()) { setShowWhenLocked(true) setTurnScreenOn(true) - window.insetsController?.hide(WindowInsets.Type.statusBars()) - window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) - } else if (isOreoMr1Plus()) { - setShowWhenLocked(true) - setTurnScreenOn(true) - window.addFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN or - WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS - ) } else { window.addFlags( WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or - WindowManager.LayoutParams.FLAG_FULLSCREEN or - WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + WindowManager.LayoutParams.FLAG_FULLSCREEN ) } - selectPhotoTab() } private fun selectPhotoTab(triggerListener: Boolean = false) { @@ -142,11 +137,14 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera checkButtons() } toggleBottomButtons(false) - } - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - if (hasStorageAndCameraPermissions()) { mOrientationEventListener.enable() } + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + ensureTransparentNavigationBar() + } + + private fun ensureTransparentNavigationBar() { + window.navigationBarColor = ContextCompat.getColor(this, android.R.color.transparent) } override fun onPause() { @@ -274,36 +272,25 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera setContentView(R.layout.activity_main) initButtons() - (toggle_flash.layoutParams as ConstraintLayout.LayoutParams).setMargins( - 0, - statusBarHeight, - 0, - 0, - ) + ViewCompat.setOnApplyWindowInsetsListener(view_holder) { _, windowInsets -> + val safeInsetBottom = windowInsets.displayCutout?.safeInsetBottom ?: 0 + val safeInsetTop = windowInsets.displayCutout?.safeInsetTop ?: 0 - (flash_toggle_group.layoutParams as ConstraintLayout.LayoutParams).setMargins( - 0, - statusBarHeight, - 0, - 0, - ) + listOf(toggle_flash, flash_toggle_group, media_size_toggle_group).forEach { view -> + view.updateLayoutParams { + topMargin = safeInsetTop + } + } - (media_size_toggle_group.layoutParams as ConstraintLayout.LayoutParams).setMargins( - 0, - statusBarHeight, - 0, - 0, - ) + val marginBottom = safeInsetBottom + navigationBarHeight + resources.getDimensionPixelSize(R.dimen.bigger_margin) + (shutter.layoutParams as ConstraintLayout.LayoutParams).goneBottomMargin = marginBottom - val goneMargin = (navigationBarHeight + resources.getDimension(R.dimen.bigger_margin)).toInt() - (shutter.layoutParams as ConstraintLayout.LayoutParams).goneBottomMargin = goneMargin + video_rec_curr_timer.updateLayoutParams { + bottomMargin = marginBottom + } - (video_rec_curr_timer.layoutParams as ConstraintLayout.LayoutParams).setMargins( - 0, - 0, - 0, - (navigationBarHeight + resources.getDimension(R.dimen.bigger_margin)).toInt() - ) + WindowInsetsCompat.CONSUMED + } checkVideoCaptureIntent() if (mIsInPhotoMode) { @@ -372,7 +359,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera private fun toggleFlash() { if (checkCameraAvailable()) { - showFlashOptions(mIsInPhotoMode) + showFlashOptions(mIsInPhotoMode) } } @@ -749,11 +736,11 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera showResolutionOptions() } - private fun createButton(resolutionOption: ResolutionOption): MaterialButton { + private fun createButton(resolutionOption: ResolutionOption): MaterialButton? { val params = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT).apply { weight = 1f } - return (layoutInflater.inflate(R.layout.layout_button, null) as MaterialButton).apply { + return (layoutInflater.inflate(R.layout.layout_button, null) as? MaterialButton)?.apply { layoutParams = params icon = AppCompatResources.getDrawable(context, resolutionOption.imageDrawableResId) id = resolutionOption.buttonViewId 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 5e5d1c9a..198a942e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ImageQualityManager.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/ImageQualityManager.kt @@ -63,14 +63,14 @@ class ImageQualityManager( .flatMap { it.qualities } .sortedByDescending { it.pixels } .distinctBy { it.getAspectRatio(activity) } - .filter { it.isSupported() } + .filter { it.isSupported(fullScreenSize.isSixteenToNine()) } } private fun getFullScreenResolution(cameraSelector: CameraSelector): MySize { return imageQualities.filter { it.camSelector == cameraSelector } .flatMap { it.qualities } .sortedByDescending { it.width } - .first { it.isSupported() } + .first { it.isSupported(false) } .copy(isFullScreen = true) } } 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 158c91d0..56e587d3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/models/MySize.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/models/MySize.kt @@ -18,7 +18,7 @@ data class MySize(val width: Int, val height: Int, val isFullScreen: Boolean = f val megaPixels: String = String.format("%.1f", (width * height.toFloat()) / ONE_MEGA_PIXEL) - private fun isSixteenToNine() = ratio == 16 / 9f + fun isSixteenToNine() = ratio == 16 / 9f private fun isFiveToThree() = ratio == 5 / 3f @@ -40,8 +40,12 @@ data class MySize(val width: Int, val height: Int, val isFullScreen: Boolean = f private fun isSquare() = width == height - fun isSupported(): Boolean { - return (isFourToThree() || isSixteenToNine() || isSquare()) && megaPixels != ZERO_MEGA_PIXEL + fun isSupported(isFullScreenSize16x9: Boolean): Boolean { + return if (isFullScreenSize16x9) { + isFourToThree() || isSquare() + } else { + isFourToThree() || isSixteenToNine() || isSquare() + } && megaPixels != ZERO_MEGA_PIXEL } fun getAspectRatio(context: Context) = when { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 53ab12f8..f49b5346 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" - android:fitsSystemWindows="false"> + android:fitsSystemWindows="true"> + android:background="@android:color/black" /> diff --git a/app/src/main/res/layout/layout_button.xml b/app/src/main/res/layout/layout_button.xml index ff7bd6a1..d92e459b 100644 --- a/app/src/main/res/layout/layout_button.xml +++ b/app/src/main/res/layout/layout_button.xml @@ -1,5 +1,5 @@ - - - - - + #2e000000 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 4b6004f7..11da72ab 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -4,12 +4,10 @@