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" /> - +