Skip to content

Commit

Permalink
settings screen constraints (#247)
Browse files Browse the repository at this point in the history
* Enable and disable default settings based on lens constraints and other selected default settings
  • Loading branch information
Kimblebee authored Aug 26, 2024
1 parent 8311848 commit 9a74762
Show file tree
Hide file tree
Showing 8 changed files with 861 additions and 203 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

const val TARGET_FPS_NONE = 0
const val TARGET_FPS_15 = 15
const val TARGET_FPS_30 = 30
const val TARGET_FPS_60 = 60

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import androidx.datastore.dataStoreFile
import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import com.google.jetpackcamera.settings.model.DEFAULT_CAMERA_APP_SETTINGS
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.LensFacing
import com.google.jetpackcamera.settings.model.TYPICAL_SYSTEM_CONSTRAINTS
Expand Down Expand Up @@ -85,10 +84,7 @@ internal class CameraAppSettingsViewModelTest {
}

assertThat(uiState).isEqualTo(
SettingsUiState.Enabled(
cameraAppSettings = DEFAULT_CAMERA_APP_SETTINGS,
systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS
)
TYPICAL_SETTINGS_UISTATE
)
}

Expand All @@ -99,8 +95,8 @@ internal class CameraAppSettingsViewModelTest {
it is SettingsUiState.Enabled
}

val initialCameraLensFacing = assertIsEnabled(initialState)
.cameraAppSettings.cameraLensFacing
val initialCameraLensFacing =
assertIsEnabled(initialState).lensFlipUiState.currentLensFacing
val nextCameraLensFacing = if (initialCameraLensFacing == LensFacing.FRONT) {
LensFacing.BACK
} else {
Expand All @@ -111,7 +107,7 @@ internal class CameraAppSettingsViewModelTest {
advanceUntilIdle()

assertIsEnabled(settingsViewModel.settingsUiState.value).also {
assertThat(it.cameraAppSettings.cameraLensFacing).isEqualTo(nextCameraLensFacing)
assertThat(it.lensFlipUiState.currentLensFacing).isEqualTo(nextCameraLensFacing)
}
}

Expand All @@ -122,14 +118,20 @@ internal class CameraAppSettingsViewModelTest {
it is SettingsUiState.Enabled
}

val initialDarkMode = assertIsEnabled(initialState).cameraAppSettings.darkMode
val initialDarkMode =
(assertIsEnabled(initialState).darkModeUiState as DarkModeUiState.Enabled)
.currentDarkMode

settingsViewModel.setDarkMode(DarkMode.DARK)

advanceUntilIdle()

val newDarkMode = assertIsEnabled(settingsViewModel.settingsUiState.value)
.cameraAppSettings.darkMode
val newDarkMode =
(
assertIsEnabled(settingsViewModel.settingsUiState.value)
.darkModeUiState as DarkModeUiState.Enabled
)
.currentDarkMode

assertEquals(initialDarkMode, DarkMode.SYSTEM)
assertEquals(DarkMode.DARK, newDarkMode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,10 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import com.google.jetpackcamera.settings.model.AspectRatio
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.LensFacing
import com.google.jetpackcamera.settings.model.Stabilization
import com.google.jetpackcamera.settings.model.TYPICAL_SYSTEM_CONSTRAINTS
import com.google.jetpackcamera.settings.ui.AspectRatioSetting
import com.google.jetpackcamera.settings.ui.CaptureModeSetting
import com.google.jetpackcamera.settings.ui.DarkModeSetting
Expand Down Expand Up @@ -130,55 +128,40 @@ fun SettingsList(
SectionHeader(title = stringResource(id = R.string.section_title_camera_settings))

DefaultCameraFacing(
settingValue = (uiState.cameraAppSettings.cameraLensFacing == LensFacing.FRONT),
enabled = with(uiState.systemConstraints.availableLenses) {
size > 1 && contains(LensFacing.FRONT)
},
lensUiState = uiState.lensFlipUiState,
setDefaultLensFacing = setDefaultLensFacing
)

FlashModeSetting(
currentFlashMode = uiState.cameraAppSettings.flashMode,
flashUiState = uiState.flashUiState,
setFlashMode = setFlashMode
)

TargetFpsSetting(
currentTargetFps = uiState.cameraAppSettings.targetFrameRate,
supportedFps = uiState.systemConstraints.perLensConstraints.values.fold(emptySet()) {
union, constraints ->
union + constraints.supportedFixedFrameRates
},
fpsUiState = uiState.fpsUiState,
setTargetFps = setTargetFrameRate
)

AspectRatioSetting(
currentAspectRatio = uiState.cameraAppSettings.aspectRatio,
aspectRatioUiState = uiState.aspectRatioUiState,
setAspectRatio = setAspectRatio
)

CaptureModeSetting(
currentCaptureMode = uiState.cameraAppSettings.captureMode,
captureModeUiState = uiState.captureModeUiState,
setCaptureMode = setCaptureMode
)

StabilizationSetting(
currentVideoStabilization = uiState.cameraAppSettings.videoCaptureStabilization,
currentPreviewStabilization = uiState.cameraAppSettings.previewStabilization,
currentTargetFps = uiState.cameraAppSettings.targetFrameRate,
supportedStabilizationMode = uiState.systemConstraints.perLensConstraints.values.fold(
emptySet()
) {
union, constraints ->
union + constraints.supportedStabilizationModes
},
stabilizationUiState = uiState.stabilizationUiState,
setVideoStabilization = setVideoStabilization,
setPreviewStabilization = setPreviewStabilization
)

SectionHeader(title = stringResource(id = R.string.section_title_app_settings))

DarkModeSetting(
currentDarkMode = uiState.cameraAppSettings.darkMode,
darkModeUiState = uiState.darkModeUiState,
setDarkMode = setDarkMode
)

Expand All @@ -190,6 +173,8 @@ fun SettingsList(
)
}

// will allow you to open stabilization popup or give disabled rationale

data class VersionInfoHolder(
val versionName: String,
val buildType: String
Expand All @@ -201,10 +186,7 @@ data class VersionInfoHolder(
private fun Preview_SettingsScreen() {
SettingsPreviewTheme {
SettingsScreen(
uiState = SettingsUiState.Enabled(
DEFAULT_CAMERA_APP_SETTINGS,
TYPICAL_SYSTEM_CONSTRAINTS
),
uiState = TYPICAL_SETTINGS_UISTATE,
versionInfo = VersionInfoHolder(
versionName = "1.0.0",
buildType = "release"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,199 @@
*/
package com.google.jetpackcamera.settings

import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.SystemConstraints
import com.google.jetpackcamera.settings.DisabledRationale.DeviceUnsupportedRationale
import com.google.jetpackcamera.settings.DisabledRationale.LensUnsupportedRationale
import com.google.jetpackcamera.settings.model.AspectRatio
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.LensFacing
import com.google.jetpackcamera.settings.model.Stabilization
import com.google.jetpackcamera.settings.ui.DEVICE_UNSUPPORTED_TAG
import com.google.jetpackcamera.settings.ui.FPS_UNSUPPORTED_TAG
import com.google.jetpackcamera.settings.ui.LENS_UNSUPPORTED_TAG
import com.google.jetpackcamera.settings.ui.STABILIZATION_UNSUPPORTED_TAG

/**
* Defines the current state of the [SettingsScreen].
*/
sealed interface SettingsUiState {
object Disabled : SettingsUiState
data object Disabled : SettingsUiState
data class Enabled(
val cameraAppSettings: CameraAppSettings,
val systemConstraints: SystemConstraints
val aspectRatioUiState: AspectRatioUiState,
val captureModeUiState: CaptureModeUiState,
val darkModeUiState: DarkModeUiState,
val flashUiState: FlashUiState,
val fpsUiState: FpsUiState,
val lensFlipUiState: FlipLensUiState,
val stabilizationUiState: StabilizationUiState
) : SettingsUiState
}

/** State for the individual options on Popup dialog settings */
sealed interface SingleSelectableState {
data object Selectable : SingleSelectableState
data class Disabled(val disabledRationale: DisabledRationale) : SingleSelectableState
}

/** Contains information on why a setting is disabled */
// TODO(b/360921588): Display information on UI regarding disabled rationale
sealed interface DisabledRationale {
val affectedSettingNameResId: Int
val reasonTextResId: Int
val testTag: String

/**
* Text will be [affectedSettingNameResId] is [R.string.device_unsupported]
*/
data class DeviceUnsupportedRationale(override val affectedSettingNameResId: Int) :
DisabledRationale {
override val reasonTextResId: Int = R.string.device_unsupported
override val testTag = DEVICE_UNSUPPORTED_TAG
}

data class FpsUnsupportedRationale(
override val affectedSettingNameResId: Int,
val currentFps: Int
) : DisabledRationale {
override val reasonTextResId: Int = R.string.fps_unsupported
override val testTag = FPS_UNSUPPORTED_TAG
}

data class StabilizationUnsupportedRationale(override val affectedSettingNameResId: Int) :
DisabledRationale {
override val reasonTextResId = R.string.stabilization_unsupported
override val testTag = STABILIZATION_UNSUPPORTED_TAG
}

sealed interface LensUnsupportedRationale : DisabledRationale {
data class FrontLensUnsupportedRationale(override val affectedSettingNameResId: Int) :
LensUnsupportedRationale {
override val reasonTextResId: Int = R.string.front_lens_unsupported
override val testTag = LENS_UNSUPPORTED_TAG
}

data class RearLensUnsupportedRationale(override val affectedSettingNameResId: Int) :
LensUnsupportedRationale {
override val reasonTextResId: Int = R.string.rear_lens_unsupported
override val testTag = LENS_UNSUPPORTED_TAG
}
}
}

fun getLensUnsupportedRationale(
lensFacing: LensFacing,
affectedSettingNameResId: Int
): LensUnsupportedRationale {
return when (lensFacing) {
LensFacing.BACK -> LensUnsupportedRationale.RearLensUnsupportedRationale(
affectedSettingNameResId
)

LensFacing.FRONT -> LensUnsupportedRationale.FrontLensUnsupportedRationale(
affectedSettingNameResId
)
}
}

/* Settings that currently have constraints **/

sealed interface FpsUiState {
data class Enabled(
val currentSelection: Int,
val fpsAutoState: SingleSelectableState,
val fpsFifteenState: SingleSelectableState,
val fpsThirtyState: SingleSelectableState,
val fpsSixtyState: SingleSelectableState,
// Contains text like "Selected FPS only supported by rear lens"
val additionalContext: String = ""
) : FpsUiState

// FPS selection completely disabled. Cannot open dialog.
data class Disabled(val disabledRationale: DisabledRationale) : FpsUiState
}

sealed interface FlipLensUiState {
val currentLensFacing: LensFacing

data class Enabled(
override val currentLensFacing: LensFacing
) : FlipLensUiState

data class Disabled(
override val currentLensFacing: LensFacing,
val disabledRationale: DisabledRationale
) : FlipLensUiState
}

sealed interface StabilizationUiState {
data class Enabled(
val currentPreviewStabilization: Stabilization,
val currentVideoStabilization: Stabilization,
val stabilizationOnState: SingleSelectableState,
val stabilizationHighQualityState: SingleSelectableState,
// Contains text like "Selected stabilization mode only supported by rear lens"
val additionalContext: String = ""
) : StabilizationUiState

// Stabilization selection completely disabled. Cannot open dialog.
data class Disabled(val disabledRationale: DisabledRationale) : StabilizationUiState
}

/* Settings that don't currently depend on constraints */

// this could be constrained w/ a check to see if a torch is available?
sealed interface FlashUiState {
data class Enabled(
val currentFlashMode: FlashMode,
val additionalContext: String = ""
) : FlashUiState
}

sealed interface AspectRatioUiState {
data class Enabled(
val currentAspectRatio: AspectRatio,
val additionalContext: String = ""
) : AspectRatioUiState
}

sealed interface CaptureModeUiState {
data class Enabled(
val currentCaptureMode: CaptureMode,
val additionalContext: String = ""
) : CaptureModeUiState
}

sealed interface DarkModeUiState {
data class Enabled(
val currentDarkMode: DarkMode,
val additionalContext: String = ""
) : DarkModeUiState
}

/**
* Settings Ui State for testing, based on Typical System Constraints.
* @see[com.google.jetpackcamera.settings.model.SystemConstraints]
*/
val TYPICAL_SETTINGS_UISTATE = SettingsUiState.Enabled(
aspectRatioUiState = AspectRatioUiState.Enabled(DEFAULT_CAMERA_APP_SETTINGS.aspectRatio),
captureModeUiState = CaptureModeUiState.Enabled(DEFAULT_CAMERA_APP_SETTINGS.captureMode),
darkModeUiState = DarkModeUiState.Enabled(DEFAULT_CAMERA_APP_SETTINGS.darkMode),
flashUiState =
FlashUiState.Enabled(currentFlashMode = DEFAULT_CAMERA_APP_SETTINGS.flashMode),
fpsUiState = FpsUiState.Enabled(
currentSelection = DEFAULT_CAMERA_APP_SETTINGS.targetFrameRate,
fpsAutoState = SingleSelectableState.Selectable,
fpsFifteenState = SingleSelectableState.Selectable,
fpsThirtyState = SingleSelectableState.Selectable,
fpsSixtyState = SingleSelectableState.Disabled(
DeviceUnsupportedRationale(R.string.fps_rationale_prefix)
)
),
lensFlipUiState = FlipLensUiState.Enabled(DEFAULT_CAMERA_APP_SETTINGS.cameraLensFacing),
stabilizationUiState =
StabilizationUiState.Disabled(
DeviceUnsupportedRationale(R.string.stabilization_rationale_prefix)
)
)
Loading

0 comments on commit 9a74762

Please sign in to comment.