Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class MainActivity : AppCompatActivity() {
LocalManagerProvider {
BitwardenTheme(
theme = state.theme,
dynamicColor = state.isDynamicColorsEnabled,
) {
RootNavScreen(
navController = navController,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
Expand All @@ -25,20 +26,25 @@ class MainViewModel @Inject constructor(
) : BaseViewModel<MainState, MainEvent, MainAction>(
MainState(
theme = settingsRepository.appTheme,
isDynamicColorsEnabled = settingsRepository.isDynamicColorsEnabled,
),
) {

init {
settingsRepository
.appThemeStateFlow
.onEach { trySendAction(MainAction.Internal.ThemeUpdate(it)) }
.map { MainAction.Internal.ThemeUpdate(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
settingsRepository
.isDynamicColorsEnabledFlow
.map { MainAction.Internal.DynamicColorUpdate(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)

settingsRepository
.isScreenCaptureAllowedStateFlow
.onEach { isAllowed ->
sendEvent(MainEvent.ScreenCaptureSettingChange(isAllowed))
}
.map { MainEvent.ScreenCaptureSettingChange(it) }
.onEach(::sendEvent)
.launchIn(viewModelScope)
viewModelScope.launch {
configRepository.getServerConfig(forceRefresh = false)
Expand All @@ -47,17 +53,28 @@ class MainViewModel @Inject constructor(

override fun handleAction(action: MainAction) {
when (action) {
is MainAction.Internal.ThemeUpdate -> handleThemeUpdated(action)
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
is MainAction.ReceiveNewIntent -> handleNewIntentReceived(action)
MainAction.OpenDebugMenu -> handleOpenDebugMenu()
is MainAction.Internal -> handleInternalAction(action)
}
}

private fun handleInternalAction(action: MainAction.Internal) {
when (action) {
is MainAction.Internal.DynamicColorUpdate -> handleDynamicColorUpdate(action)
is MainAction.Internal.ThemeUpdate -> handleThemeUpdated(action)
}
}

private fun handleOpenDebugMenu() {
sendEvent(MainEvent.NavigateToDebugMenu)
}

private fun handleDynamicColorUpdate(action: MainAction.Internal.DynamicColorUpdate) {
mutableStateFlow.update { it.copy(isDynamicColorsEnabled = action.isEnabled) }
}

private fun handleThemeUpdated(action: MainAction.Internal.ThemeUpdate) {
mutableStateFlow.update { it.copy(theme = action.theme) }
sendEvent(MainEvent.UpdateAppTheme(osTheme = action.theme.osValue))
Expand Down Expand Up @@ -91,6 +108,7 @@ class MainViewModel @Inject constructor(
@Parcelize
data class MainState(
val theme: AppTheme,
val isDynamicColorsEnabled: Boolean,
) : Parcelable

/**
Expand All @@ -116,6 +134,12 @@ sealed class MainAction {
* Actions for internal use by the ViewModel.
*/
sealed class Internal : MainAction() {
/**
* Indicates that dynamic colors have been enabled or disabled.
*/
data class DynamicColorUpdate(
val isEnabled: Boolean,
) : Internal()

/**
* Indicates that the app theme has changed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ interface SettingsDiskSource {
*/
val defaultSaveOptionFlow: Flow<DefaultSaveOption>

/**
* The currently persisted dynamic colors setting (or `null` if not set).
*/
var isDynamicColorsEnabled: Boolean?

/**
* Emits updates that track [isDynamicColorsEnabled].
*/
val isDynamicColorsEnabledFlow: Flow<Boolean?>

/**
* The currently persisted biometric integrity source for the system.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.onSubscription
private const val APP_THEME_KEY = "theme"
private const val APP_LANGUAGE_KEY = "appLocale"
private const val DEFAULT_SAVE_OPTION_KEY = "defaultSaveOption"
private const val DYNAMIC_COLORS_KEY = "dynamicColors"
private const val SYSTEM_BIOMETRIC_INTEGRITY_SOURCE_KEY = "biometricIntegritySource"
private const val ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY = "accountBiometricIntegrityValid"
private const val ALERT_THRESHOLD_SECONDS_KEY = "alertThresholdSeconds"
Expand Down Expand Up @@ -48,6 +49,8 @@ class SettingsDiskSourceImpl(
private val mutableDefaultSaveOptionFlow =
bufferedMutableSharedFlow<DefaultSaveOption>()

private val mutableDynamicColorsFlow = bufferedMutableSharedFlow<Boolean?>()

override var appLanguage: AppLanguage?
get() = getString(key = APP_LANGUAGE_KEY)
?.let { storedValue ->
Expand Down Expand Up @@ -98,6 +101,16 @@ class SettingsDiskSourceImpl(
get() = mutableDefaultSaveOptionFlow
.onSubscription { emit(defaultSaveOption) }

override var isDynamicColorsEnabled: Boolean?
get() = getBoolean(key = DYNAMIC_COLORS_KEY)
set(newValue) {
putBoolean(key = DYNAMIC_COLORS_KEY, value = newValue)
mutableDynamicColorsFlow.tryEmit(newValue)
}

override val isDynamicColorsEnabledFlow: Flow<Boolean?>
get() = mutableDynamicColorsFlow.onSubscription { emit(isDynamicColorsEnabled) }

override var systemBiometricIntegritySource: String?
get() = getString(key = SYSTEM_BIOMETRIC_INTEGRITY_SOURCE_KEY)
set(value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ interface SettingsRepository {
*/
var defaultSaveOption: DefaultSaveOption

/**
* The current setting for enabling dynamic colors.
*/
var isDynamicColorsEnabled: Boolean

/**
* Tracks changes to the [isDynamicColorsEnabled] value.
*/
val isDynamicColorsEnabledFlow: StateFlow<Boolean>

/**
* Flow that emits changes to [defaultSaveOption]
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ class SettingsRepositoryImpl(
override val defaultSaveOptionFlow: Flow<DefaultSaveOption>
by settingsDiskSource::defaultSaveOptionFlow

override var isDynamicColorsEnabled: Boolean
get() = settingsDiskSource.isDynamicColorsEnabled ?: false
set(value) {
settingsDiskSource.isDynamicColorsEnabled = value
}

override val isDynamicColorsEnabledFlow: StateFlow<Boolean>
get() = settingsDiskSource
.isDynamicColorsEnabledFlow
.map { it ?: false }
.stateIn(
scope = unconfinedScope,
started = SharingStarted.Eagerly,
initialValue = isDynamicColorsEnabled,
)

override val isUnlockWithBiometricsEnabled: Boolean
get() = authDiskSource.getUserBiometricUnlockKey() != null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,19 @@ fun SettingsScreen(
)
Spacer(modifier = Modifier.height(16.dp))
AppearanceSettings(
state = state,
state = state.appearance,
onThemeSelection = remember(viewModel) {
{
viewModel.trySendAction(SettingsAction.AppearanceChange.ThemeChange(it))
}
},
onDynamicColorChange = remember(viewModel) {
{
viewModel.trySendAction(
SettingsAction.AppearanceChange.DynamicColorChange(it),
)
}
},
)
Spacer(Modifier.height(16.dp))
HelpSettings(
Expand Down Expand Up @@ -518,8 +525,9 @@ private fun ScreenCaptureRow(

@Composable
private fun ColumnScope.AppearanceSettings(
state: SettingsState,
state: SettingsState.Appearance,
onThemeSelection: (theme: AppTheme) -> Unit,
onDynamicColorChange: (isEnabled: Boolean) -> Unit,
) {
BitwardenListHeaderText(
modifier = Modifier
Expand All @@ -529,19 +537,33 @@ private fun ColumnScope.AppearanceSettings(
)
Spacer(modifier = Modifier.height(height = 8.dp))
ThemeSelectionRow(
currentSelection = state.appearance.theme,
currentSelection = state.theme,
onThemeSelection = onThemeSelection,
cardStyle = if (state.isDynamicColorsSupported) CardStyle.Top() else CardStyle.Full,
modifier = Modifier
.testTag("ThemeChooser")
.standardHorizontalMargin()
.fillMaxWidth(),
)
if (state.isDynamicColorsSupported) {
BitwardenSwitch(
label = stringResource(id = BitwardenString.use_dynamic_colors),
isChecked = state.isDynamicColorsEnabled,
onCheckedChange = onDynamicColorChange,
cardStyle = CardStyle.Bottom,
modifier = Modifier
.testTag(tag = "DynamicColorsSwitch")
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
}

@Composable
private fun ThemeSelectionRow(
currentSelection: AppTheme,
onThemeSelection: (AppTheme) -> Unit,
cardStyle: CardStyle,
modifier: Modifier = Modifier,
resources: Resources = LocalResources.current,
) {
Expand All @@ -555,7 +577,7 @@ private fun ThemeSelectionRow(
.first { it.displayLabel(resources) == selectedOptionLabel }
onThemeSelection(selectedOption)
},
cardStyle = CardStyle.Full,
cardStyle = cardStyle,
modifier = modifier,
)
}
Expand Down
Loading
Loading