From bf60f8f9e3225dbd85cc5131ca7db6e927d93a9e Mon Sep 17 00:00:00 2001 From: David Perez Date: Wed, 22 Jan 2025 14:02:18 -0600 Subject: [PATCH] PM-17404: Set app delegate on theme change (#4605) --- .../java/com/x8bit/bitwarden/MainActivity.kt | 17 +++- .../java/com/x8bit/bitwarden/MainViewModel.kt | 20 ++++ .../datasource/disk/SettingsDiskSource.kt | 5 + .../datasource/disk/SettingsDiskSourceImpl.kt | 6 +- .../platform/repository/SettingsRepository.kt | 5 + .../repository/SettingsRepositoryImpl.kt | 10 ++ .../appearance/AppearanceViewModel.kt | 7 -- .../settings/appearance/model/AppTheme.kt | 10 +- .../com/x8bit/bitwarden/MainViewModelTest.kt | 94 +++++++++++++------ .../datasource/disk/SettingsDiskSourceTest.kt | 14 +++ .../disk/util/FakeSettingsDiskSource.kt | 16 +++- .../repository/SettingsRepositoryTest.kt | 9 ++ .../appearance/AppearanceViewModelTest.kt | 29 ++---- 13 files changed, 173 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/MainActivity.kt b/app/src/main/java/com/x8bit/bitwarden/MainActivity.kt index 175487965b..6341a2bfa6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/MainActivity.kt +++ b/app/src/main/java/com/x8bit/bitwarden/MainActivity.kt @@ -66,13 +66,14 @@ class MainActivity : AppCompatActivity() { ) } - // Within the app the language will change dynamically and will be managed - // by the OS, but we need to ensure we properly set the language when - // upgrading from older versions that handle this differently. + // Within the app the language and theme will change dynamically and will be managed by the + // OS, but we need to ensure we properly set the values when upgrading from older versions + // that handle this differently or when the activity restarts. settingsRepository.appLanguage.localeName?.let { localeName -> val localeList = LocaleListCompat.forLanguageTags(localeName) AppCompatDelegate.setApplicationLocales(localeList) } + AppCompatDelegate.setDefaultNightMode(settingsRepository.appTheme.osValue) setContent { val state by mainViewModel.stateFlow.collectAsStateWithLifecycle() val navController = rememberNavController() @@ -94,6 +95,16 @@ class MainActivity : AppCompatActivity() { ) .show() } + + is MainEvent.UpdateAppLocale -> { + AppCompatDelegate.setApplicationLocales( + LocaleListCompat.forLanguageTags(event.localeName), + ) + } + + is MainEvent.UpdateAppTheme -> { + AppCompatDelegate.setDefaultNightMode(event.osTheme) + } } } updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed) diff --git a/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt index 36855feb2c..2e04819679 100644 --- a/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt @@ -108,6 +108,11 @@ class MainViewModel @Inject constructor( .appThemeStateFlow .onEach { trySendAction(MainAction.Internal.ThemeUpdate(it)) } .launchIn(viewModelScope) + settingsRepository + .appLanguageStateFlow + .map { MainEvent.UpdateAppLocale(it.localeName) } + .onEach(::sendEvent) + .launchIn(viewModelScope) settingsRepository .isScreenCaptureAllowedStateFlow @@ -211,6 +216,7 @@ class MainViewModel @Inject constructor( private fun handleAppThemeUpdated(action: MainAction.Internal.ThemeUpdate) { mutableStateFlow.update { it.copy(theme = action.theme) } + sendEvent(MainEvent.UpdateAppTheme(osTheme = action.theme.osValue)) } private fun handleVaultUnlockStateChange() { @@ -518,4 +524,18 @@ sealed class MainEvent { * Show a toast with the given [message]. */ data class ShowToast(val message: Text) : MainEvent() + + /** + * Indicates that the app language has been updated. + */ + data class UpdateAppLocale( + val localeName: String?, + ) : MainEvent() + + /** + * Indicates that the app theme has been updated. + */ + data class UpdateAppTheme( + val osTheme: Int, + ) : MainEvent() } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt index b6ae8fcf51..89632a875a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt @@ -18,6 +18,11 @@ interface SettingsDiskSource { */ var appLanguage: AppLanguage? + /** + * Emits updates that track [AppLanguage]. + */ + val appLanguageFlow: Flow + /** * Has the initial autofill dialog been shown to the user. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt index 686dcced8f..09f17081df 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt @@ -10,7 +10,6 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppThem import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.onSubscription -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.Instant @@ -50,6 +49,7 @@ class SettingsDiskSourceImpl( private val json: Json, ) : BaseDiskSource(sharedPreferences = sharedPreferences), SettingsDiskSource { + private val mutableAppLanguageFlow = bufferedMutableSharedFlow(replay = 1) private val mutableAppThemeFlow = bufferedMutableSharedFlow(replay = 1) private val mutableLastSyncFlowMap = mutableMapOf>() @@ -94,8 +94,12 @@ class SettingsDiskSourceImpl( key = APP_LANGUAGE_KEY, value = value?.localeName, ) + mutableAppLanguageFlow.tryEmit(value) } + override val appLanguageFlow: Flow + get() = mutableAppLanguageFlow.onSubscription { emit(appLanguage) } + override var initialAutofillDialogShown: Boolean? get() = getBoolean(key = INITIAL_AUTOFILL_DIALOG_SHOWN) set(value) { diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt index 3fc66076af..b7fc2e845a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt @@ -23,6 +23,11 @@ interface SettingsRepository { */ var appLanguage: AppLanguage + /** + * Tracks changes to the [AppLanguage]. + */ + val appLanguageStateFlow: StateFlow + /** * The currently stored [AppTheme]. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt index d30332d00a..045f42678e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt @@ -64,6 +64,16 @@ class SettingsRepositoryImpl( settingsDiskSource.appLanguage = value } + override val appLanguageStateFlow: StateFlow + get() = settingsDiskSource + .appLanguageFlow + .map { it ?: AppLanguage.DEFAULT } + .stateIn( + scope = unconfinedScope, + started = SharingStarted.Eagerly, + initialValue = appLanguage, + ) + override var appTheme: AppTheme by settingsDiskSource::appTheme override val appThemeStateFlow: StateFlow diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModel.kt index cb61078c1d..08e538eb70 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModel.kt @@ -1,8 +1,6 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.appearance import android.os.Parcelable -import androidx.appcompat.app.AppCompatDelegate -import androidx.core.os.LocaleListCompat import androidx.lifecycle.SavedStateHandle import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.ui.platform.base.BaseViewModel @@ -44,11 +42,6 @@ class AppearanceViewModel @Inject constructor( private fun handleLanguageChanged(action: AppearanceAction.LanguageChange) { mutableStateFlow.update { it.copy(language = action.language) } settingsRepository.appLanguage = action.language - - val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags( - action.language.localeName, - ) - AppCompatDelegate.setApplicationLocales(appLocale) } private fun handleShowWebsiteIconsToggled(action: AppearanceAction.ShowWebsiteIconsToggle) { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/model/AppTheme.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/model/AppTheme.kt index b9e88ebc48..add1ee815c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/model/AppTheme.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/model/AppTheme.kt @@ -1,12 +1,14 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model +import androidx.appcompat.app.AppCompatDelegate + /** * Represents the theme options the user can set. * * The [value] is used for consistent storage purposes. */ -enum class AppTheme(val value: String?) { - DEFAULT(value = null), - DARK(value = "dark"), - LIGHT(value = "light"), +enum class AppTheme(val value: String?, val osValue: Int) { + DEFAULT(value = null, osValue = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM), + DARK(value = "dark", osValue = AppCompatDelegate.MODE_NIGHT_YES), + LIGHT(value = "light", osValue = AppCompatDelegate.MODE_NIGHT_NO), } diff --git a/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt index ce5b518106..bf53db8e4d 100644 --- a/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt @@ -50,6 +50,7 @@ import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.util.asText +import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.util.isAccountSecurityShortcut @@ -85,10 +86,12 @@ class MainViewModelTest : BaseViewModelTest() { private val addTotpItemAuthenticatorManager = AddTotpItemFromAuthenticatorManagerImpl() private val mutableUserStateFlow = MutableStateFlow(null) private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT) + private val mutableAppLanguageFlow = MutableStateFlow(AppLanguage.DEFAULT) private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true) private val settingsRepository = mockk { every { appTheme } returns AppTheme.DEFAULT every { appThemeStateFlow } returns mutableAppThemeFlow + every { appLanguageStateFlow } returns mutableAppLanguageFlow every { isScreenCaptureAllowed } returns true every { isScreenCaptureAllowedStateFlow } returns mutableScreenCaptureAllowedFlow every { storeUserHasLoggedInValue(any()) } just runs @@ -190,6 +193,10 @@ class MainViewModelTest : BaseViewModelTest() { val viewModel = createViewModel() viewModel.eventFlow.test { + // We skip the first 2 events because they are the default appTheme and appLanguage + awaitItem() + awaitItem() + mutableUserStateFlow.value = UserState( activeUserId = userId1, accounts = listOf( @@ -237,6 +244,10 @@ class MainViewModelTest : BaseViewModelTest() { val viewModel = createViewModel() viewModel.eventFlow.test { + // We skip the first 2 events because they are the default appTheme and appLanguage + awaitItem() + awaitItem() + mutableVaultStateEventFlow.tryEmit(VaultStateEvent.Unlocked(userId = "userId")) expectNoEvents() @@ -254,6 +265,10 @@ class MainViewModelTest : BaseViewModelTest() { val viewModel = createViewModel() val cipherView = mockk() viewModel.eventFlow.test { + // We skip the first 2 events because they are the default appTheme and appLanguage + awaitItem() + awaitItem() + accessibilitySelectionManager.emitAccessibilitySelection(cipherView = cipherView) assertEquals( MainEvent.CompleteAccessibilityAutofill(cipherView = cipherView), @@ -267,6 +282,10 @@ class MainViewModelTest : BaseViewModelTest() { val viewModel = createViewModel() val cipherView = mockk() viewModel.eventFlow.test { + // We skip the first 2 events because they are the default appTheme and appLanguage + awaitItem() + awaitItem() + autofillSelectionManager.emitAutofillSelection(cipherView = cipherView) assertEquals( MainEvent.CompleteAutofill(cipherView = cipherView), @@ -291,28 +310,44 @@ class MainViewModelTest : BaseViewModelTest() { } @Test - fun `on AppThemeChanged should update state`() { + fun `on AppThemeChanged should update state and send event`() = runTest { + val theme = AppTheme.DARK val viewModel = createViewModel() - assertEquals( - DEFAULT_STATE, - viewModel.stateFlow.value, - ) - viewModel.trySendAction( - MainAction.Internal.ThemeUpdate( - theme = AppTheme.DARK, - ), - ) - assertEquals( - DEFAULT_STATE.copy( - theme = AppTheme.DARK, - ), - viewModel.stateFlow.value, - ) + viewModel.stateEventFlow(backgroundScope) { stateFlow, eventFlow -> + // We skip the first 2 events because they are the default appTheme and appLanguage + eventFlow.awaitItem() + eventFlow.awaitItem() + + assertEquals(DEFAULT_STATE, stateFlow.awaitItem()) + mutableAppThemeFlow.value = theme + assertEquals(DEFAULT_STATE.copy(theme = theme), stateFlow.awaitItem()) + assertEquals(MainEvent.UpdateAppTheme(osTheme = theme.osValue), eventFlow.awaitItem()) + } verify { settingsRepository.appTheme settingsRepository.appThemeStateFlow + settingsRepository.appLanguageStateFlow + } + } + + @Test + fun `on AppLanguageChanged should send UpdateAppLocale event`() = runTest { + val language = AppLanguage.ENGLISH_BRITISH + val viewModel = createViewModel() + + viewModel.eventFlow.test { + // We skip the first 2 events because they are the default appTheme and appLanguage + awaitItem() + awaitItem() + + mutableAppLanguageFlow.value = language + assertEquals(MainEvent.UpdateAppLocale(localeName = language.localeName), awaitItem()) + } + + verify(exactly = 1) { + settingsRepository.appLanguageStateFlow } } @@ -511,12 +546,12 @@ class MainViewModelTest : BaseViewModelTest() { ) } returns EmailTokenResult.Error(message = null) - viewModel.trySendAction( - MainAction.ReceiveFirstIntent( - intent = mockIntent, - ), - ) viewModel.eventFlow.test { + // We skip the first 2 events because they are the default appTheme and appLanguage + awaitItem() + awaitItem() + + viewModel.trySendAction(MainAction.ReceiveFirstIntent(intent = mockIntent)) assertEquals( MainEvent.ShowToast(R.string.there_was_an_issue_validating_the_registration_token.asText()), awaitItem(), @@ -548,12 +583,12 @@ class MainViewModelTest : BaseViewModelTest() { ) } returns EmailTokenResult.Error(message = expectedMessage) - viewModel.trySendAction( - MainAction.ReceiveFirstIntent( - intent = mockIntent, - ), - ) viewModel.eventFlow.test { + // We skip the first 2 events because they are the default appTheme and appLanguage + awaitItem() + awaitItem() + + viewModel.trySendAction(MainAction.ReceiveFirstIntent(intent = mockIntent)) assertEquals( MainEvent.ShowToast(expectedMessage.asText()), awaitItem(), @@ -957,9 +992,12 @@ class MainViewModelTest : BaseViewModelTest() { @Test fun `send NavigateToDebugMenu action when OpenDebugMenu action is sent`() = runTest { val viewModel = createViewModel() - viewModel.trySendAction(MainAction.OpenDebugMenu) - viewModel.eventFlow.test { + // We skip the first 2 events because they are the default appTheme and appLanguage + awaitItem() + awaitItem() + + viewModel.trySendAction(MainAction.OpenDebugMenu) assertEquals(MainEvent.NavigateToDebugMenu, awaitItem()) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt index f51d6403a0..56ffce26b9 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt @@ -51,6 +51,20 @@ class SettingsDiskSourceTest { ) } + @Test + fun `appLanguageFlow should react to changes in appLanguage`() = runTest { + val appLanguage = AppLanguage.ENGLISH_BRITISH + settingsDiskSource.appLanguageFlow.test { + // The initial values of the Flow and the property are in sync + assertNull(settingsDiskSource.appLanguage) + assertNull(awaitItem()) + + // Updating the repository updates shared preferences + settingsDiskSource.appLanguage = appLanguage + assertEquals(appLanguage, awaitItem()) + } + } + @Test fun `setting appLanguage should update SharedPreferences`() { val appLanguageKey = "bwPreferencesStorage:appLocale" diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt index 35db4baec4..4c2f7d6459 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt @@ -16,8 +16,9 @@ import java.time.Instant */ class FakeSettingsDiskSource : SettingsDiskSource { - private val mutableAppThemeFlow = - bufferedMutableSharedFlow(replay = 1) + private val mutableAppLanguageFlow = bufferedMutableSharedFlow(replay = 1) + + private val mutableAppThemeFlow = bufferedMutableSharedFlow(replay = 1) private val mutableLastSyncCallFlowMap = mutableMapOf>() @@ -42,6 +43,7 @@ class FakeSettingsDiskSource : SettingsDiskSource { private val mutableScreenCaptureAllowedFlowMap = mutableMapOf>() + private var storedAppLanguage: AppLanguage? = null private var storedAppTheme: AppTheme = AppTheme.DEFAULT private val storedLastSyncTime = mutableMapOf() private val storedVaultTimeoutActions = mutableMapOf() @@ -81,7 +83,15 @@ class FakeSettingsDiskSource : SettingsDiskSource { private val mutableVaultRegisteredForExportFlowMap = mutableMapOf>() - override var appLanguage: AppLanguage? = null + override var appLanguage: AppLanguage? + get() = storedAppLanguage + set(value) { + storedAppLanguage = value + mutableAppLanguageFlow.tryEmit(value) + } + + override val appLanguageFlow: Flow + get() = mutableAppLanguageFlow.onSubscription { emit(appLanguage) } override var appTheme: AppTheme get() = storedAppTheme diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt index 41925f53ab..9f4163b9af 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt @@ -263,6 +263,15 @@ class SettingsRepositoryTest { ) } + @Test + fun `appLanguageStateFlow should react to changes in SettingsDiskSource`() = runTest { + settingsRepository.appLanguageStateFlow.test { + assertEquals(AppLanguage.DEFAULT, awaitItem()) + fakeSettingsDiskSource.appLanguage = AppLanguage.DUTCH + assertEquals(AppLanguage.DUTCH, awaitItem()) + } + } + @Test fun `vaultLastSync should pull from and update SettingsDiskSource`() { fakeAuthDiskSource.userState = MOCK_USER_STATE diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModelTest.kt index c26ab608e9..f65c110748 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/appearance/AppearanceViewModelTest.kt @@ -1,6 +1,5 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.appearance -import androidx.appcompat.app.AppCompatDelegate import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test import com.x8bit.bitwarden.data.platform.repository.SettingsRepository @@ -10,14 +9,10 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppThem import io.mockk.every import io.mockk.just import io.mockk.mockk -import io.mockk.mockkStatic import io.mockk.runs -import io.mockk.unmockkStatic import io.mockk.verify import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test class AppearanceViewModelTest : BaseViewModelTest() { @@ -30,16 +25,6 @@ class AppearanceViewModelTest : BaseViewModelTest() { every { appTheme = AppTheme.DARK } just runs } - @BeforeEach - fun setup() { - mockkStatic(AppCompatDelegate::setApplicationLocales) - } - - @AfterEach - fun teardown() { - unmockkStatic(AppCompatDelegate::setApplicationLocales) - } - @Test fun `initial state should be correct when not set`() { val viewModel = createViewModel(state = null) @@ -72,9 +57,7 @@ class AppearanceViewModelTest : BaseViewModelTest() { DEFAULT_STATE, awaitItem(), ) - viewModel.trySendAction( - AppearanceAction.LanguageChange(AppLanguage.ENGLISH), - ) + viewModel.trySendAction(AppearanceAction.LanguageChange(AppLanguage.ENGLISH)) assertEquals( DEFAULT_STATE.copy( language = AppLanguage.ENGLISH, @@ -82,8 +65,8 @@ class AppearanceViewModelTest : BaseViewModelTest() { awaitItem(), ) } + verify { - AppCompatDelegate.setApplicationLocales(any()) mockSettingsRepository.appLanguage mockSettingsRepository.appLanguage = AppLanguage.ENGLISH } @@ -126,11 +109,11 @@ class AppearanceViewModelTest : BaseViewModelTest() { DEFAULT_STATE.copy(theme = AppTheme.DARK), awaitItem(), ) + } - verify { - mockSettingsRepository.appTheme - mockSettingsRepository.appTheme = AppTheme.DARK - } + verify(exactly = 1) { + mockSettingsRepository.appTheme + mockSettingsRepository.appTheme = AppTheme.DARK } }