summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKimberly Crevecoeur <kcrevecoeur@google.com>2024-01-31 18:28:15 -0800
committerGitHub <noreply@github.com>2024-01-31 18:28:15 -0800
commitaa8801977aa9586d9c7693d09c410201d5a1743e (patch)
tree77212ff65a35cc1593b5ac140d92b22269f39dfe
parent75b6e3fb5fd1dfee19db651d5e428a61c4ad446e (diff)
downloadjetpack-camera-app-aa8801977aa9586d9c7693d09c410201d5a1743e.tar.gz
Settings to enable preview/video stabilization (#83)
Add stabilization setting datastore and ui * Popup dialog setting in settings screen; options enabled/disabled based on device capability * UI icon on preview screen indicating when stabilization is active
-rw-r--r--data/settings/src/main/java/com/google/jetpackcamera/settings/JcaSettingsSerializer.kt4
-rw-r--r--data/settings/src/main/java/com/google/jetpackcamera/settings/LocalSettingsRepository.kt66
-rw-r--r--data/settings/src/main/java/com/google/jetpackcamera/settings/SettingsRepository.kt8
-rw-r--r--data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt5
-rw-r--r--data/settings/src/main/java/com/google/jetpackcamera/settings/model/Stabilization.kt48
-rw-r--r--data/settings/src/main/java/com/google/jetpackcamera/settings/model/SupportedStabilizationMode.kt25
-rw-r--r--data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeJcaSettingsSerializer.kt14
-rw-r--r--data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeSettingsRepository.kt46
-rw-r--r--data/settings/src/main/proto/com/google/jetpackcamera/settings/jca_settings.proto8
-rw-r--r--data/settings/src/main/proto/com/google/jetpackcamera/settings/preview_stabilization.proto26
-rw-r--r--data/settings/src/main/proto/com/google/jetpackcamera/settings/video_stabilization.proto26
-rw-r--r--domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraXCameraUseCase.kt80
-rw-r--r--feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt11
-rw-r--r--feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt27
-rw-r--r--feature/preview/src/main/res/drawable/baseline_video_stable_24.xml21
-rw-r--r--feature/preview/src/main/res/values/strings.xml3
-rw-r--r--feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt11
-rw-r--r--feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsViewModel.kt39
-rw-r--r--feature/settings/src/main/java/com/google/jetpackcamera/settings/ui/SettingsComponents.kt158
-rw-r--r--feature/settings/src/main/res/values/strings.xml16
20 files changed, 603 insertions, 39 deletions
diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/JcaSettingsSerializer.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/JcaSettingsSerializer.kt
index e607a6b..2530a2b 100644
--- a/data/settings/src/main/java/com/google/jetpackcamera/settings/JcaSettingsSerializer.kt
+++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/JcaSettingsSerializer.kt
@@ -31,6 +31,10 @@ object JcaSettingsSerializer : Serializer<JcaSettings> {
.setFlashModeStatus(FlashMode.FLASH_MODE_OFF)
.setAspectRatioStatus(AspectRatio.ASPECT_RATIO_NINE_SIXTEEN)
.setCaptureModeStatus(CaptureMode.CAPTURE_MODE_MULTI_STREAM)
+ .setStabilizePreview(PreviewStabilization.PREVIEW_STABILIZATION_UNDEFINED)
+ .setStabilizeVideo(VideoStabilization.VIDEO_STABILIZATION_UNDEFINED)
+ .setStabilizePreviewSupported(false)
+ .setStabilizeVideoSupported(false)
.build()
override suspend fun readFrom(input: InputStream): JcaSettings {
diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/LocalSettingsRepository.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/LocalSettingsRepository.kt
index fb03ba9..f54a1a9 100644
--- a/data/settings/src/main/java/com/google/jetpackcamera/settings/LocalSettingsRepository.kt
+++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/LocalSettingsRepository.kt
@@ -20,11 +20,15 @@ import com.google.jetpackcamera.settings.AspectRatio as AspectRatioProto
import com.google.jetpackcamera.settings.CaptureMode as CaptureModeProto
import com.google.jetpackcamera.settings.DarkMode as DarkModeProto
import com.google.jetpackcamera.settings.FlashMode as FlashModeProto
+import com.google.jetpackcamera.settings.PreviewStabilization as PreviewStabilizationProto
+import com.google.jetpackcamera.settings.VideoStabilization as VideoStabilizationProto
import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.FlashMode
+import com.google.jetpackcamera.settings.model.Stabilization
+import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@@ -55,6 +59,12 @@ class LocalSettingsRepository @Inject constructor(
isFrontCameraAvailable = it.frontCameraAvailable,
isBackCameraAvailable = it.backCameraAvailable,
aspectRatio = AspectRatio.fromProto(it.aspectRatioStatus),
+ previewStabilization = Stabilization.fromProto(it.stabilizePreview),
+ videoCaptureStabilization = Stabilization.fromProto(it.stabilizeVideo),
+ supportedStabilizationModes = getSupportedStabilization(
+ previewSupport = it.stabilizePreviewSupported,
+ videoSupport = it.stabilizeVideoSupported
+ ),
captureMode = when (it.captureModeStatus) {
CaptureModeProto.CAPTURE_MODE_SINGLE_STREAM -> CaptureMode.SINGLE_STREAM
CaptureModeProto.CAPTURE_MODE_MULTI_STREAM -> CaptureMode.MULTI_STREAM
@@ -143,4 +153,60 @@ class LocalSettingsRepository @Inject constructor(
.build()
}
}
+
+ override suspend fun updatePreviewStabilization(stabilization: Stabilization) {
+ val newStatus = when (stabilization) {
+ Stabilization.ON -> PreviewStabilizationProto.PREVIEW_STABILIZATION_ON
+ Stabilization.OFF -> PreviewStabilizationProto.PREVIEW_STABILIZATION_OFF
+ else -> PreviewStabilizationProto.PREVIEW_STABILIZATION_UNDEFINED
+ }
+ jcaSettings.updateData { currentSettings ->
+ currentSettings.toBuilder()
+ .setStabilizePreview(newStatus)
+ .build()
+ }
+ }
+
+ override suspend fun updateVideoStabilization(stabilization: Stabilization) {
+ val newStatus = when (stabilization) {
+ Stabilization.ON -> VideoStabilizationProto.VIDEO_STABILIZATION_ON
+ Stabilization.OFF -> VideoStabilizationProto.VIDEO_STABILIZATION_OFF
+ else -> VideoStabilizationProto.VIDEO_STABILIZATION_UNDEFINED
+ }
+ jcaSettings.updateData { currentSettings ->
+ currentSettings.toBuilder()
+ .setStabilizeVideo(newStatus)
+ .build()
+ }
+ }
+
+ override suspend fun updateVideoStabilizationSupported(isSupported: Boolean) {
+ jcaSettings.updateData { currentSettings ->
+ currentSettings.toBuilder()
+ .setStabilizeVideoSupported(isSupported)
+ .build()
+ }
+ }
+
+ override suspend fun updatePreviewStabilizationSupported(isSupported: Boolean) {
+ jcaSettings.updateData { currentSettings ->
+ currentSettings.toBuilder()
+ .setStabilizeVideoSupported(isSupported)
+ .build()
+ }
+ }
+
+ private fun getSupportedStabilization(
+ previewSupport: Boolean,
+ videoSupport: Boolean
+ ): List<SupportedStabilizationMode> {
+ return buildList {
+ if (previewSupport && videoSupport) {
+ add(SupportedStabilizationMode.ON)
+ }
+ if (!previewSupport && videoSupport) {
+ add(SupportedStabilizationMode.HIGH_QUALITY)
+ }
+ }
+ }
}
diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/SettingsRepository.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/SettingsRepository.kt
index 637a1ab..0f622d1 100644
--- a/data/settings/src/main/java/com/google/jetpackcamera/settings/SettingsRepository.kt
+++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/SettingsRepository.kt
@@ -20,6 +20,7 @@ import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.FlashMode
+import com.google.jetpackcamera.settings.model.Stabilization
import kotlinx.coroutines.flow.Flow
/**
@@ -42,5 +43,12 @@ interface SettingsRepository {
suspend fun updateCaptureMode(captureMode: CaptureMode)
+ suspend fun updatePreviewStabilization(stabilization: Stabilization)
+ suspend fun updateVideoStabilization(stabilization: Stabilization)
+
+ suspend fun updateVideoStabilizationSupported(isSupported: Boolean)
+
+ suspend fun updatePreviewStabilizationSupported(isSupported: Boolean)
+
suspend fun getCameraAppSettings(): CameraAppSettings
}
diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt
index e16f332..e135089 100644
--- a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt
+++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt
@@ -25,7 +25,10 @@ data class CameraAppSettings(
val darkMode: DarkMode = DarkMode.SYSTEM,
val flashMode: FlashMode = FlashMode.OFF,
val captureMode: CaptureMode = CaptureMode.MULTI_STREAM,
- val aspectRatio: AspectRatio = AspectRatio.NINE_SIXTEEN
+ val aspectRatio: AspectRatio = AspectRatio.NINE_SIXTEEN,
+ val previewStabilization: Stabilization = Stabilization.UNDEFINED,
+ val videoCaptureStabilization: Stabilization = Stabilization.UNDEFINED,
+ val supportedStabilizationModes: List<SupportedStabilizationMode> = emptyList()
)
val DEFAULT_CAMERA_APP_SETTINGS = CameraAppSettings()
diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Stabilization.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Stabilization.kt
new file mode 100644
index 0000000..b0b599e
--- /dev/null
+++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/Stabilization.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.jetpackcamera.settings.model
+
+import com.google.jetpackcamera.settings.PreviewStabilization as PreviewStabilizationProto
+import com.google.jetpackcamera.settings.VideoStabilization as VideoStabilizationProto
+
+enum class Stabilization {
+ UNDEFINED,
+ OFF,
+ ON;
+
+ companion object {
+ /** returns the Stabilization enum equivalent of a provided [PreviewStabilizationProto]. */
+ fun fromProto(stabilizationProto: PreviewStabilizationProto): Stabilization {
+ return when (stabilizationProto) {
+ PreviewStabilizationProto.PREVIEW_STABILIZATION_UNDEFINED -> UNDEFINED
+ PreviewStabilizationProto.PREVIEW_STABILIZATION_OFF -> OFF
+ PreviewStabilizationProto.PREVIEW_STABILIZATION_ON -> ON
+ else -> UNDEFINED
+ }
+ }
+
+ /** returns the Stabilization enum equivalent of a provided [VideoStabilizationProto]. */
+
+ fun fromProto(stabilizationProto: VideoStabilizationProto): Stabilization {
+ return when (stabilizationProto) {
+ VideoStabilizationProto.VIDEO_STABILIZATION_UNDEFINED -> UNDEFINED
+ VideoStabilizationProto.VIDEO_STABILIZATION_OFF -> OFF
+ VideoStabilizationProto.VIDEO_STABILIZATION_ON -> ON
+ else -> UNDEFINED
+ }
+ }
+ }
+}
diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/SupportedStabilizationMode.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/SupportedStabilizationMode.kt
new file mode 100644
index 0000000..9cdc8f7
--- /dev/null
+++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/SupportedStabilizationMode.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.jetpackcamera.settings.model
+
+/** Enum class representing the device's supported video stabilization configurations. */
+enum class SupportedStabilizationMode {
+ /** Device supports Preview stabilization. */
+ ON,
+
+ /** Device supports Video stabilization.*/
+ HIGH_QUALITY
+}
diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeJcaSettingsSerializer.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeJcaSettingsSerializer.kt
index 2193922..c2033bd 100644
--- a/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeJcaSettingsSerializer.kt
+++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeJcaSettingsSerializer.kt
@@ -17,8 +17,13 @@ package com.google.jetpackcamera.settings.test
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
+import com.google.jetpackcamera.settings.AspectRatio
+import com.google.jetpackcamera.settings.CaptureMode
import com.google.jetpackcamera.settings.DarkMode
+import com.google.jetpackcamera.settings.FlashMode
import com.google.jetpackcamera.settings.JcaSettings
+import com.google.jetpackcamera.settings.PreviewStabilization
+import com.google.jetpackcamera.settings.VideoStabilization
import com.google.protobuf.InvalidProtocolBufferException
import java.io.IOException
import java.io.InputStream
@@ -31,6 +36,15 @@ class FakeJcaSettingsSerializer(
override val defaultValue: JcaSettings = JcaSettings.newBuilder()
.setDarkModeStatus(DarkMode.DARK_MODE_SYSTEM)
.setDefaultFrontCamera(false)
+ .setBackCameraAvailable(true)
+ .setFrontCameraAvailable(true)
+ .setFlashModeStatus(FlashMode.FLASH_MODE_OFF)
+ .setAspectRatioStatus(AspectRatio.ASPECT_RATIO_NINE_SIXTEEN)
+ .setCaptureModeStatus(CaptureMode.CAPTURE_MODE_MULTI_STREAM)
+ .setStabilizePreview(PreviewStabilization.PREVIEW_STABILIZATION_UNDEFINED)
+ .setStabilizeVideo(VideoStabilization.VIDEO_STABILIZATION_UNDEFINED)
+ .setStabilizeVideoSupported(false)
+ .setStabilizePreviewSupported(false)
.build()
override suspend fun readFrom(input: InputStream): JcaSettings {
diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeSettingsRepository.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeSettingsRepository.kt
index fbc40f5..2bb4295 100644
--- a/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeSettingsRepository.kt
+++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeSettingsRepository.kt
@@ -22,11 +22,15 @@ import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DEFAULT_CAMERA_APP_SETTINGS
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.FlashMode
+import com.google.jetpackcamera.settings.model.Stabilization
+import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
object FakeSettingsRepository : SettingsRepository {
var currentCameraSettings: CameraAppSettings = DEFAULT_CAMERA_APP_SETTINGS
+ private var isPreviewStabilizationSupported: Boolean = false
+ private var isVideoStabilizationSupported: Boolean = false
override val cameraAppSettings: Flow<CameraAppSettings> = flow { emit(currentCameraSettings) }
@@ -35,8 +39,8 @@ object FakeSettingsRepository : SettingsRepository {
currentCameraSettings = currentCameraSettings.copy(isFrontCameraFacing = newLensFacing)
}
- override suspend fun updateDarkModeStatus(darkmode: DarkMode) {
- currentCameraSettings = currentCameraSettings.copy(darkMode = darkmode)
+ override suspend fun updateDarkModeStatus(darkMode: DarkMode) {
+ currentCameraSettings = currentCameraSettings.copy(darkMode = darkMode)
}
override suspend fun updateFlashModeStatus(flashMode: FlashMode) {
@@ -62,7 +66,43 @@ object FakeSettingsRepository : SettingsRepository {
currentCameraSettings.copy(captureMode = captureMode)
}
+ override suspend fun updatePreviewStabilization(stabilization: Stabilization) {
+ currentCameraSettings =
+ currentCameraSettings.copy(previewStabilization = stabilization)
+ }
+
+ override suspend fun updateVideoStabilization(stabilization: Stabilization) {
+ currentCameraSettings =
+ currentCameraSettings.copy(videoCaptureStabilization = stabilization)
+ }
+
+ override suspend fun updateVideoStabilizationSupported(isSupported: Boolean) {
+ isVideoStabilizationSupported = isSupported
+ setSupportedStabilizationMode()
+ }
+
+ override suspend fun updatePreviewStabilizationSupported(isSupported: Boolean) {
+ isPreviewStabilizationSupported = isSupported
+ setSupportedStabilizationMode()
+ }
+
+ private fun setSupportedStabilizationMode() {
+ val stabilizationModes =
+ buildList {
+ if (isPreviewStabilizationSupported) {
+ add(SupportedStabilizationMode.ON)
+ }
+ if (isVideoStabilizationSupported) {
+ add(SupportedStabilizationMode.HIGH_QUALITY)
+ }
+ }
+
+ currentCameraSettings =
+ currentCameraSettings.copy(supportedStabilizationModes = stabilizationModes)
+ }
+
override suspend fun updateAspectRatio(aspectRatio: AspectRatio) {
- TODO("Not yet implemented")
+ currentCameraSettings =
+ currentCameraSettings.copy(aspectRatio = aspectRatio)
}
}
diff --git a/data/settings/src/main/proto/com/google/jetpackcamera/settings/jca_settings.proto b/data/settings/src/main/proto/com/google/jetpackcamera/settings/jca_settings.proto
index 7e27529..288d501 100644
--- a/data/settings/src/main/proto/com/google/jetpackcamera/settings/jca_settings.proto
+++ b/data/settings/src/main/proto/com/google/jetpackcamera/settings/jca_settings.proto
@@ -20,6 +20,10 @@ import "com/google/jetpackcamera/settings/aspect_ratio.proto";
import "com/google/jetpackcamera/settings/capture_mode.proto";
import "com/google/jetpackcamera/settings/dark_mode.proto";
import "com/google/jetpackcamera/settings/flash_mode.proto";
+import "com/google/jetpackcamera/settings/preview_stabilization.proto";
+import "com/google/jetpackcamera/settings/video_stabilization.proto";
+
+
option java_package = "com.google.jetpackcamera.settings";
@@ -33,4 +37,8 @@ message JcaSettings {
FlashMode flash_mode_status = 6;
AspectRatio aspect_ratio_status = 7;
CaptureMode capture_mode_status = 8;
+ PreviewStabilization stabilize_preview = 9;
+ VideoStabilization stabilize_video = 10;
+ bool stabilize_video_supported = 11;
+ bool stabilize_preview_supported = 12;
} \ No newline at end of file
diff --git a/data/settings/src/main/proto/com/google/jetpackcamera/settings/preview_stabilization.proto b/data/settings/src/main/proto/com/google/jetpackcamera/settings/preview_stabilization.proto
new file mode 100644
index 0000000..f0cf902
--- /dev/null
+++ b/data/settings/src/main/proto/com/google/jetpackcamera/settings/preview_stabilization.proto
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+option java_package = "com.google.jetpackcamera.settings";
+option java_multiple_files = true;
+
+enum PreviewStabilization {
+ PREVIEW_STABILIZATION_UNDEFINED = 0;
+ PREVIEW_STABILIZATION_OFF = 1;
+ PREVIEW_STABILIZATION_ON = 2;
+} \ No newline at end of file
diff --git a/data/settings/src/main/proto/com/google/jetpackcamera/settings/video_stabilization.proto b/data/settings/src/main/proto/com/google/jetpackcamera/settings/video_stabilization.proto
new file mode 100644
index 0000000..66e1a8b
--- /dev/null
+++ b/data/settings/src/main/proto/com/google/jetpackcamera/settings/video_stabilization.proto
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+option java_package = "com.google.jetpackcamera.settings";
+option java_multiple_files = true;
+
+enum VideoStabilization {
+ VIDEO_STABILIZATION_UNDEFINED = 0;
+ VIDEO_STABILIZATION_OFF = 1;
+ VIDEO_STABILIZATION_ON = 2;
+} \ No newline at end of file
diff --git a/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraXCameraUseCase.kt b/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraXCameraUseCase.kt
index 3187a2b..aa0f372 100644
--- a/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraXCameraUseCase.kt
+++ b/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraXCameraUseCase.kt
@@ -51,6 +51,8 @@ import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.FlashMode
+import com.google.jetpackcamera.settings.model.Stabilization
+import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
import dagger.hilt.android.scopes.ViewModelScoped
import java.io.FileNotFoundException
import java.lang.RuntimeException
@@ -91,7 +93,7 @@ constructor(
private val recorder = Recorder.Builder().setExecutor(
defaultDispatcher.asExecutor()
).build()
- private val videoCaptureUseCase = VideoCapture.withOutput(recorder)
+ private lateinit var videoCaptureUseCase: VideoCapture<Recorder>
private var recording: Recording? = null
private lateinit var previewUseCase: Preview
@@ -99,7 +101,10 @@ constructor(
private lateinit var aspectRatio: AspectRatio
private lateinit var captureMode: CaptureMode
+ private lateinit var stabilizePreviewMode: Stabilization
+ private lateinit var stabilizeVideoMode: Stabilization
private lateinit var surfaceProvider: Preview.SurfaceProvider
+ private lateinit var supportedStabilizationModes: List<SupportedStabilizationMode>
private var isFrontFacing = true
private val screenFlashEvents: MutableSharedFlow<CameraUseCase.ScreenFlashEvent> =
@@ -108,10 +113,11 @@ constructor(
override suspend fun initialize(currentCameraSettings: CameraAppSettings): List<Int> {
this.aspectRatio = currentCameraSettings.aspectRatio
this.captureMode = currentCameraSettings.captureMode
+ this.stabilizePreviewMode = currentCameraSettings.previewStabilization
+ this.stabilizeVideoMode = currentCameraSettings.videoCaptureStabilization
+ this.supportedStabilizationModes = currentCameraSettings.supportedStabilizationModes
setFlashMode(currentCameraSettings.flashMode, currentCameraSettings.isFrontCameraFacing)
-
cameraProvider = ProcessCameraProvider.getInstance(application).await()
- updateUseCaseGroup()
val availableCameraLens =
listOf(
@@ -120,15 +126,16 @@ constructor(
).filter { lensFacing ->
cameraProvider.hasCamera(cameraLensToSelector(lensFacing))
}
-
// updates values for available camera lens if necessary
coroutineScope {
settingsRepository.updateAvailableCameraLens(
availableCameraLens.contains(CameraSelector.LENS_FACING_FRONT),
availableCameraLens.contains(CameraSelector.LENS_FACING_BACK)
)
+ settingsRepository.updateVideoStabilizationSupported(isStabilizationSupported())
}
-
+ videoCaptureUseCase = createVideoUseCase()
+ updateUseCaseGroup()
return availableCameraLens
}
@@ -410,25 +417,78 @@ constructor(
useCaseGroup = useCaseGroupBuilder.build()
}
- private fun createPreviewUseCase(): Preview {
+ /**
+ * Checks if video stabilization is supported by the device.
+ *
+ */
+ private fun isStabilizationSupported(): Boolean {
val availableCameraInfo = cameraProvider.availableCameraInfos
val cameraSelector = if (isFrontFacing) {
CameraSelector.DEFAULT_FRONT_CAMERA
} else {
CameraSelector.DEFAULT_BACK_CAMERA
}
- val isPreviewStabilizationSupported =
+ val isVideoStabilizationSupported =
cameraSelector.filter(availableCameraInfo).firstOrNull()?.let {
- Preview.getPreviewCapabilities(it).isStabilizationSupported
+ Recorder.getVideoCapabilities(it).isStabilizationSupported
} ?: false
+ return isVideoStabilizationSupported
+ }
+
+ private fun createVideoUseCase(): VideoCapture<Recorder> {
+ val videoCaptureBuilder = VideoCapture.Builder(recorder)
+
+ // set video stabilization
+
+ if (shouldVideoBeStabilized()) {
+ val isStabilized = when (stabilizeVideoMode) {
+ Stabilization.ON -> true
+ Stabilization.OFF, Stabilization.UNDEFINED -> false
+ }
+ videoCaptureBuilder.setVideoStabilizationEnabled(isStabilized)
+ }
+ return videoCaptureBuilder.build()
+ }
+
+ private fun shouldVideoBeStabilized(): Boolean {
+ // video is supported by the device AND
+ // video is on OR preview is on
+ return (supportedStabilizationModes.contains(SupportedStabilizationMode.HIGH_QUALITY)) &&
+ (
+ // high quality (video only) selected
+ (
+ stabilizeVideoMode == Stabilization.ON &&
+ stabilizePreviewMode == Stabilization.UNDEFINED
+ ) ||
+ // or on is selected
+ (
+ stabilizePreviewMode == Stabilization.ON &&
+ stabilizeVideoMode != Stabilization.OFF
+ )
+ )
+ }
+
+ private fun createPreviewUseCase(): Preview {
val previewUseCaseBuilder = Preview.Builder()
- if (isPreviewStabilizationSupported) {
- previewUseCaseBuilder.setPreviewStabilizationEnabled(true)
+ // set preview stabilization
+ if (shouldPreviewBeStabilized()) {
+ val isStabilized = when (stabilizePreviewMode) {
+ Stabilization.ON -> true
+ else -> false
+ }
+ previewUseCaseBuilder.setPreviewStabilizationEnabled(isStabilized)
}
return previewUseCaseBuilder.build()
}
+ private fun shouldPreviewBeStabilized(): Boolean {
+ return (
+ supportedStabilizationModes.contains(SupportedStabilizationMode.ON) &&
+ stabilizePreviewMode == Stabilization.ON
+ )
+ }
+
// converts LensFacing from datastore to @LensFacing Int value
private fun getLensFacing(isFrontFacing: Boolean): Int = when (isFrontFacing) {
true -> CameraSelector.LENS_FACING_FRONT
diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt
index d8d90b2..c5cfdd2 100644
--- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt
+++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt
@@ -64,6 +64,7 @@ import com.google.jetpackcamera.feature.preview.ui.PreviewDisplay
import com.google.jetpackcamera.feature.preview.ui.ScreenFlashScreen
import com.google.jetpackcamera.feature.preview.ui.SettingsNavButton
import com.google.jetpackcamera.feature.preview.ui.ShowTestableToast
+import com.google.jetpackcamera.feature.preview.ui.StabilizationIcon
import com.google.jetpackcamera.feature.preview.ui.TestingButton
import com.google.jetpackcamera.feature.preview.ui.ZoomScaleText
import com.google.jetpackcamera.feature.quicksettings.QuickSettingsScreenOverlay
@@ -206,7 +207,7 @@ fun PreviewScreen(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
- horizontalArrangement = Arrangement.Center,
+ horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
TestingButton(
@@ -223,6 +224,14 @@ fun PreviewScreen(
}
)
)
+ StabilizationIcon(
+ supportedStabilizationMode = previewUiState
+ .currentCameraSettings.supportedStabilizationModes,
+ videoStabilization = previewUiState
+ .currentCameraSettings.videoCaptureStabilization,
+ previewStabilization = previewUiState
+ .currentCameraSettings.previewStabilization
+ )
}
}
}
diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt
index 5c7b5ab..01f09c8 100644
--- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt
+++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt
@@ -45,7 +45,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -55,12 +54,15 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.jetpackcamera.feature.preview.R
import com.google.jetpackcamera.feature.preview.VideoRecordingState
import com.google.jetpackcamera.settings.model.AspectRatio
+import com.google.jetpackcamera.settings.model.Stabilization
+import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
import com.google.jetpackcamera.viewfinder.CameraPreview
import kotlinx.coroutines.CompletableDeferred
@@ -182,6 +184,29 @@ fun PreviewDisplay(
}
}
+@Composable
+fun StabilizationIcon(
+ supportedStabilizationMode: List<SupportedStabilizationMode>,
+ videoStabilization: Stabilization,
+ previewStabilization: Stabilization
+) {
+ if (supportedStabilizationMode.isNotEmpty() &&
+ (videoStabilization == Stabilization.ON || previewStabilization == Stabilization.ON)
+ ) {
+ val descriptionText = if (videoStabilization == Stabilization.ON) {
+ stringResource(id = R.string.stabilization_icon_description_preview_and_video)
+ } else {
+ // previewStabilization will not be on for high quality
+ stringResource(id = R.string.stabilization_icon_description_video_only)
+ }
+ Icon(
+ painter = painterResource(id = R.drawable.baseline_video_stable_24),
+ contentDescription = descriptionText,
+ tint = Color.White
+ )
+ }
+}
+
/**
* A temporary button that can be added to preview for quick testing purposes
*/
diff --git a/feature/preview/src/main/res/drawable/baseline_video_stable_24.xml b/feature/preview/src/main/res/drawable/baseline_video_stable_24.xml
new file mode 100644
index 0000000..54f9651
--- /dev/null
+++ b/feature/preview/src/main/res/drawable/baseline_video_stable_24.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
+
+ <path android:fillColor="@android:color/white" android:pathData="M20,4H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,18V6h2.95l-2.33,8.73L16.82,18H4zM20,18h-2.95l2.34,-8.73L7.18,6H20V18z"/>
+
+</vector>
diff --git a/feature/preview/src/main/res/values/strings.xml b/feature/preview/src/main/res/values/strings.xml
index b16f190..b2713f5 100644
--- a/feature/preview/src/main/res/values/strings.xml
+++ b/feature/preview/src/main/res/values/strings.xml
@@ -23,4 +23,7 @@
<string name="toast_image_capture_success">Image Capture Success</string>
<string name="toast_capture_failure">Image Capture Failure</string>
+ <string name="stabilization_icon_description_preview_and_video">Preview is Stabilized</string>
+ <string name="stabilization_icon_description_video_only">Only Video is Stabilized</string>
+
</resources>
diff --git a/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt
index c28a558..fd8b32c 100644
--- a/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt
+++ b/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt
@@ -31,6 +31,7 @@ import com.google.jetpackcamera.settings.ui.DefaultCameraFacing
import com.google.jetpackcamera.settings.ui.FlashModeSetting
import com.google.jetpackcamera.settings.ui.SectionHeader
import com.google.jetpackcamera.settings.ui.SettingsPageHeader
+import com.google.jetpackcamera.settings.ui.StabilizationSetting
/**
* Screen used for the Settings feature.
@@ -78,6 +79,16 @@ fun SettingsList(uiState: SettingsUiState, viewModel: SettingsViewModel) {
setCaptureMode = viewModel::setCaptureMode
)
+ // todo: b/313647247 - query device and disable setting if preview stabilization isn't supported.
+ // todo: b/313647809 - query device and disable setting if video stabilization isn't supported.
+ StabilizationSetting(
+ currentVideoStabilization = uiState.cameraAppSettings.videoCaptureStabilization,
+ currentPreviewStabilization = uiState.cameraAppSettings.previewStabilization,
+ supportedStabilizationMode = uiState.cameraAppSettings.supportedStabilizationModes,
+ setVideoStabilization = viewModel::setVideoStabilization,
+ setPreviewStabilization = viewModel::setPreviewStabilization
+ )
+
SectionHeader(title = stringResource(id = R.string.section_title_app_settings))
DarkModeSetting(
diff --git a/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsViewModel.kt b/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsViewModel.kt
index c0005db..ca55517 100644
--- a/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsViewModel.kt
+++ b/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsViewModel.kt
@@ -23,6 +23,7 @@ import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DEFAULT_CAMERA_APP_SETTINGS
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.FlashMode
+import com.google.jetpackcamera.settings.model.Stabilization
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -61,8 +62,7 @@ class SettingsViewModel @Inject constructor(
Log.d(
TAG,
- "updated setting" +
- settingsRepository.getCameraAppSettings().captureMode
+ "updated setting ${settingsRepository.getCameraAppSettings().captureMode}"
)
}
}
@@ -82,7 +82,7 @@ class SettingsViewModel @Inject constructor(
Log.d(
TAG,
"set camera default facing: " +
- settingsRepository.getCameraAppSettings().isFrontCameraFacing
+ "${settingsRepository.getCameraAppSettings().isFrontCameraFacing}"
)
}
}
@@ -92,8 +92,7 @@ class SettingsViewModel @Inject constructor(
settingsRepository.updateDarkModeStatus(darkMode)
Log.d(
TAG,
- "set dark mode theme: " +
- settingsRepository.getCameraAppSettings().darkMode
+ "set dark mode theme: ${settingsRepository.getCameraAppSettings().darkMode}"
)
}
}
@@ -109,7 +108,7 @@ class SettingsViewModel @Inject constructor(
settingsRepository.updateAspectRatio(aspectRatio)
Log.d(
TAG,
- "set aspect ratio " +
+ "set aspect ratio: " +
"${settingsRepository.getCameraAppSettings().aspectRatio}"
)
}
@@ -121,8 +120,32 @@ class SettingsViewModel @Inject constructor(
Log.d(
TAG,
- "set default capture mode " +
- settingsRepository.getCameraAppSettings().captureMode
+ "set default capture mode: " +
+ "${settingsRepository.getCameraAppSettings().captureMode}"
+ )
+ }
+ }
+
+ fun setPreviewStabilization(stabilization: Stabilization) {
+ viewModelScope.launch {
+ settingsRepository.updatePreviewStabilization(stabilization)
+
+ Log.d(
+ TAG,
+ "set preview stabilization: " +
+ "${settingsRepository.getCameraAppSettings().previewStabilization}"
+ )
+ }
+ }
+
+ fun setVideoStabilization(stabilization: Stabilization) {
+ viewModelScope.launch {
+ settingsRepository.updateVideoStabilization(stabilization)
+
+ Log.d(
+ TAG,
+ "set video stabilization: " +
+ "${settingsRepository.getCameraAppSettings().previewStabilization}"
)
}
}
diff --git a/feature/settings/src/main/java/com/google/jetpackcamera/settings/ui/SettingsComponents.kt b/feature/settings/src/main/java/com/google/jetpackcamera/settings/ui/SettingsComponents.kt
index c38dd58..e970216 100644
--- a/feature/settings/src/main/java/com/google/jetpackcamera/settings/ui/SettingsComponents.kt
+++ b/feature/settings/src/main/java/com/google/jetpackcamera/settings/ui/SettingsComponents.kt
@@ -20,8 +20,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.selection.toggleable
@@ -32,6 +32,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch
@@ -52,6 +53,8 @@ import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.FlashMode
+import com.google.jetpackcamera.settings.model.Stabilization
+import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
/**
* MAJOR SETTING UI COMPONENTS
@@ -211,7 +214,6 @@ fun AspectRatioSetting(currentAspectRatio: AspectRatio, setAspectRatio: (AspectR
@Composable
fun CaptureModeSetting(currentCaptureMode: CaptureMode, setCaptureMode: (CaptureMode) -> Unit) {
- // todo: string resources
BasicPopupSetting(
title = stringResource(R.string.capture_mode_title),
leadingIcon = null,
@@ -219,6 +221,7 @@ fun CaptureModeSetting(currentCaptureMode: CaptureMode, setCaptureMode: (Capture
CaptureMode.MULTI_STREAM -> stringResource(
id = R.string.capture_mode_description_multi_stream
)
+
CaptureMode.SINGLE_STREAM -> stringResource(
id = R.string.capture_mode_description_single_stream
)
@@ -241,6 +244,110 @@ fun CaptureModeSetting(currentCaptureMode: CaptureMode, setCaptureMode: (Capture
}
/**
+ * Returns the description text depending on the preview/video stabilization configuration.
+ * On - preview is on and video is NOT off.
+ * High Quality - preview is unspecified and video is ON.
+ * Off - Every other configuration.
+ */
+private fun getStabilizationStringRes(
+ previewStabilization: Stabilization,
+ videoStabilization: Stabilization
+): Int {
+ return if (previewStabilization == Stabilization.ON &&
+ videoStabilization != Stabilization.OFF
+ ) {
+ R.string.stabilization_description_on
+ } else if (previewStabilization == Stabilization.UNDEFINED &&
+ videoStabilization == Stabilization.ON
+ ) {
+ R.string.stabilization_description_high_quality
+ } else {
+ R.string.stabilization_description_off
+ }
+}
+
+/**
+ * A Setting to set preview and video stabilization.
+ *
+ * ON - Both preview and video are stabilized.
+ * HIGH_QUALITY - Video will be stabilized, preview might be stabilized, depending on the device.
+ * OFF - Preview and video stabilization is disabled.
+ *
+ * @param supportedStabilizationMode the enabled condition for this setting.
+ */
+@Composable
+fun StabilizationSetting(
+ currentPreviewStabilization: Stabilization,
+ currentVideoStabilization: Stabilization,
+ supportedStabilizationMode: List<SupportedStabilizationMode>,
+ setVideoStabilization: (Stabilization) -> Unit,
+ setPreviewStabilization: (Stabilization) -> Unit
+) {
+ BasicPopupSetting(
+ title = stringResource(R.string.video_stabilization_title),
+ leadingIcon = null,
+ enabled = supportedStabilizationMode.isNotEmpty(),
+ description = if (supportedStabilizationMode.isEmpty()) {
+ stringResource(id = R.string.stabilization_description_unsupported)
+ } else {
+ stringResource(
+ id = getStabilizationStringRes(
+ previewStabilization = currentPreviewStabilization,
+ videoStabilization = currentVideoStabilization
+ )
+ )
+ },
+ popupContents = {
+ Column(Modifier.selectableGroup()) {
+ Spacer(modifier = Modifier.height(10.dp))
+
+ // on selector
+ SingleChoiceSelector(
+ text = stringResource(id = R.string.stabilization_selector_on),
+ secondaryText = stringResource(id = R.string.stabilization_selector_on_info),
+ enabled = supportedStabilizationMode.contains(SupportedStabilizationMode.ON),
+ selected = (currentPreviewStabilization == Stabilization.ON) &&
+ (currentVideoStabilization != Stabilization.OFF),
+ onClick = {
+ setVideoStabilization(Stabilization.UNDEFINED)
+ setPreviewStabilization(Stabilization.ON)
+ }
+ )
+
+ // high quality selector
+ SingleChoiceSelector(
+ text = stringResource(id = R.string.stabilization_selector_high_quality),
+ secondaryText = stringResource(
+ id = R.string.stabilization_selector_high_quality_info
+ ),
+ enabled = supportedStabilizationMode.contains(
+ SupportedStabilizationMode.HIGH_QUALITY
+ ),
+
+ selected = (currentPreviewStabilization == Stabilization.UNDEFINED) &&
+ (currentVideoStabilization == Stabilization.ON),
+ onClick = {
+ setVideoStabilization(Stabilization.ON)
+ setPreviewStabilization(Stabilization.UNDEFINED)
+ }
+ )
+
+ // off selector
+ SingleChoiceSelector(
+ text = stringResource(id = R.string.stabilization_selector_off),
+ selected = (currentPreviewStabilization != Stabilization.ON) &&
+ (currentVideoStabilization != Stabilization.ON),
+ onClick = {
+ setVideoStabilization(Stabilization.OFF)
+ setPreviewStabilization(Stabilization.OFF)
+ }
+ )
+ }
+ }
+ )
+}
+
+/*
* Setting UI sub-Components
* small and whimsical :)
* don't use these directly, use them to build the ready-to-use setting components
@@ -261,6 +368,7 @@ fun BasicPopupSetting(
SettingUI(
modifier = modifier.clickable(enabled = enabled) { popupStatus.value = true },
title = title,
+ enabled = enabled,
description = description,
leadingIcon = leadingIcon,
trailingContent = null
@@ -303,6 +411,7 @@ fun SwitchSettingUI(
value = settingValue,
onValueChange = { _ -> onClick() }
),
+ enabled = enabled,
title = title,
description = description,
leadingIcon = leadingIcon,
@@ -325,17 +434,30 @@ fun SwitchSettingUI(
fun SettingUI(
modifier: Modifier = Modifier,
title: String,
+ enabled: Boolean = true,
description: String? = null,
leadingIcon: @Composable (() -> Unit)?,
trailingContent: @Composable (() -> Unit)?
) {
ListItem(
modifier = modifier,
- headlineContent = { Text(title) },
- supportingContent = when (description) {
- null -> null
- else -> {
- { Text(description) }
+ headlineContent = {
+ when (enabled) {
+ true -> Text(title)
+ false -> {
+ Text(text = title, color = LocalContentColor.current.copy(alpha = .7f))
+ }
+ }
+ },
+ supportingContent = {
+ if (description != null) {
+ when (enabled) {
+ true -> Text(description)
+ false -> Text(
+ text = description,
+ color = LocalContentColor.current.copy(alpha = .7f)
+ )
+ }
}
},
leadingContent = leadingIcon,
@@ -350,6 +472,7 @@ fun SettingUI(
fun SingleChoiceSelector(
modifier: Modifier = Modifier,
text: String,
+ secondaryText: String? = null,
selected: Boolean,
onClick: () -> Unit,
enabled: Boolean = true
@@ -360,16 +483,23 @@ fun SingleChoiceSelector(
.selectable(
selected = selected,
role = Role.RadioButton,
- onClick = onClick
+ onClick = onClick,
+ enabled = enabled
),
verticalAlignment = Alignment.CenterVertically
) {
- RadioButton(
- selected = selected,
- onClick = onClick,
- enabled = enabled
+ SettingUI(
+ title = text,
+ description = secondaryText,
+ enabled = enabled,
+ leadingIcon = {
+ RadioButton(
+ selected = selected,
+ onClick = onClick,
+ enabled = enabled
+ )
+ },
+ trailingContent = null
)
- Spacer(Modifier.width(8.dp))
- Text(text)
}
}
diff --git a/feature/settings/src/main/res/values/strings.xml b/feature/settings/src/main/res/values/strings.xml
index 3df861f..44a0bbc 100644
--- a/feature/settings/src/main/res/values/strings.xml
+++ b/feature/settings/src/main/res/values/strings.xml
@@ -59,7 +59,7 @@
<string name="capture_mode_description_multi_stream">Multi Stream</string>
<string name="capture_mode_description_single_stream">Single Stream</string>
- <!-- Aspect Ratio setting strings -->
+ <!-- Aspect Ratio setting strings -->
<string name="aspect_ratio_title">Set Aspect Ratio</string>
<string name="aspect_ratio_selector_9_16">9:16</string>
@@ -70,4 +70,18 @@
<string name="aspect_ratio_description_3_4">Aspect Ratio is 3:4</string>
<string name="aspect_ratio_description_1_1">Aspect Ratio is 1:1</string>
+ <!-- Stabilization setting strings -->
+ <string name="video_stabilization_title">Set Video Stabilization</string>
+
+ <string name="stabilization_selector_on">On</string>
+ <string name="stabilization_selector_high_quality">High Quality</string>
+ <string name="stabilization_selector_off">Off</string>
+
+ <string name="stabilization_selector_on_info">Both preview and video streams will be stabilized</string>
+ <string name="stabilization_selector_high_quality_info">Video stream will be stabilized, but preview might not be. This mode ensures highest-quality video stream.</string>
+
+ <string name="stabilization_description_on">Stabilization On</string>
+ <string name="stabilization_description_high_quality">Stabilization High Quality</string>
+ <string name="stabilization_description_off">Stabilization Off</string>
+ <string name="stabilization_description_unsupported">Stabilization unsupported</string>
</resources> \ No newline at end of file