mirror of
https://github.com/bitwarden/android.git
synced 2026-03-21 22:00:42 -05:00
PM-17404: Set app delegate on theme change (#4605)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@ interface SettingsDiskSource {
|
||||
*/
|
||||
var appLanguage: AppLanguage?
|
||||
|
||||
/**
|
||||
* Emits updates that track [AppLanguage].
|
||||
*/
|
||||
val appLanguageFlow: Flow<AppLanguage?>
|
||||
|
||||
/**
|
||||
* Has the initial autofill dialog been shown to the user.
|
||||
*/
|
||||
|
||||
@@ -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<AppLanguage?>(replay = 1)
|
||||
private val mutableAppThemeFlow = bufferedMutableSharedFlow<AppTheme>(replay = 1)
|
||||
|
||||
private val mutableLastSyncFlowMap = mutableMapOf<String, MutableSharedFlow<Instant?>>()
|
||||
@@ -94,8 +94,12 @@ class SettingsDiskSourceImpl(
|
||||
key = APP_LANGUAGE_KEY,
|
||||
value = value?.localeName,
|
||||
)
|
||||
mutableAppLanguageFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
override val appLanguageFlow: Flow<AppLanguage?>
|
||||
get() = mutableAppLanguageFlow.onSubscription { emit(appLanguage) }
|
||||
|
||||
override var initialAutofillDialogShown: Boolean?
|
||||
get() = getBoolean(key = INITIAL_AUTOFILL_DIALOG_SHOWN)
|
||||
set(value) {
|
||||
|
||||
@@ -23,6 +23,11 @@ interface SettingsRepository {
|
||||
*/
|
||||
var appLanguage: AppLanguage
|
||||
|
||||
/**
|
||||
* Tracks changes to the [AppLanguage].
|
||||
*/
|
||||
val appLanguageStateFlow: StateFlow<AppLanguage>
|
||||
|
||||
/**
|
||||
* The currently stored [AppTheme].
|
||||
*/
|
||||
|
||||
@@ -64,6 +64,16 @@ class SettingsRepositoryImpl(
|
||||
settingsDiskSource.appLanguage = value
|
||||
}
|
||||
|
||||
override val appLanguageStateFlow: StateFlow<AppLanguage>
|
||||
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<AppTheme>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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<UserState?>(null)
|
||||
private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT)
|
||||
private val mutableAppLanguageFlow = MutableStateFlow(AppLanguage.DEFAULT)
|
||||
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true)
|
||||
private val settingsRepository = mockk<SettingsRepository> {
|
||||
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<CipherView>()
|
||||
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<CipherView>()
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -16,8 +16,9 @@ import java.time.Instant
|
||||
*/
|
||||
class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
|
||||
private val mutableAppThemeFlow =
|
||||
bufferedMutableSharedFlow<AppTheme>(replay = 1)
|
||||
private val mutableAppLanguageFlow = bufferedMutableSharedFlow<AppLanguage?>(replay = 1)
|
||||
|
||||
private val mutableAppThemeFlow = bufferedMutableSharedFlow<AppTheme>(replay = 1)
|
||||
|
||||
private val mutableLastSyncCallFlowMap = mutableMapOf<String, MutableSharedFlow<Instant?>>()
|
||||
|
||||
@@ -42,6 +43,7 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
private val mutableScreenCaptureAllowedFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
|
||||
private var storedAppLanguage: AppLanguage? = null
|
||||
private var storedAppTheme: AppTheme = AppTheme.DEFAULT
|
||||
private val storedLastSyncTime = mutableMapOf<String, Instant?>()
|
||||
private val storedVaultTimeoutActions = mutableMapOf<String, VaultTimeoutAction?>()
|
||||
@@ -81,7 +83,15 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
private val mutableVaultRegisteredForExportFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
|
||||
override var appLanguage: AppLanguage? = null
|
||||
override var appLanguage: AppLanguage?
|
||||
get() = storedAppLanguage
|
||||
set(value) {
|
||||
storedAppLanguage = value
|
||||
mutableAppLanguageFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
override val appLanguageFlow: Flow<AppLanguage?>
|
||||
get() = mutableAppLanguageFlow.onSubscription { emit(appLanguage) }
|
||||
|
||||
override var appTheme: AppTheme
|
||||
get() = storedAppTheme
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user