feat: add persistent interface settings

This commit is contained in:
2026-08-26 12:19:38 +08:00
parent b24ca98e14
commit 283f56a3b6
7 changed files with 603 additions and 1 deletions
@@ -0,0 +1,111 @@
package com.flagship.abox.manager.ui.preferences
import com.flagship.abox.manager.R
import com.flagship.abox.manager.preferences.AppThemeMode
import com.flagship.abox.manager.preferences.DesignRenderer
import com.flagship.abox.manager.ui.common.UiText.StringResource
import com.flagship.abox.manager.preferences.NavigationPresentation
import com.flagship.abox.manager.preferences.UiPreferences
import com.flagship.abox.manager.preferences.UiPreferencesRepository
import com.flagship.abox.manager.preferences.UiPreferencesWriteResult
import com.flagship.abox.manager.testing.MainDispatcherRule
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class SettingsViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
@Test
fun rendererUpdatePersistsWithoutCallingSessionCoordinator() = runTest {
val repository = RecordingUiPreferencesRepository()
val viewModel = SettingsViewModel(repository)
advanceUntilIdle()
viewModel.onAction(SettingsUiAction.SetRenderer(DesignRenderer.MIUIX))
advanceUntilIdle()
assertEquals(1, repository.updateCalls.size)
assertEquals(DesignRenderer.MIUIX, repository.value.renderer)
assertEquals(DesignRenderer.MIUIX, viewModel.uiState.value.preferences.renderer)
}
@Test
fun writeFailureLeavesLastValueAndShowsLocalizedError() = runTest {
val repository = RecordingUiPreferencesRepository(
initial = UiPreferences(renderer = DesignRenderer.MATERIAL3),
writeResult = UiPreferencesWriteResult.Failure,
)
val viewModel = SettingsViewModel(repository)
advanceUntilIdle()
viewModel.onAction(SettingsUiAction.SetRenderer(DesignRenderer.MIUIX))
advanceUntilIdle()
assertEquals(1, repository.updateCalls.size)
assertEquals(DesignRenderer.MATERIAL3, viewModel.uiState.value.preferences.renderer)
assertTrue(viewModel.uiState.value.error is StringResource)
assertEquals(
R.string.settings_error_save,
(viewModel.uiState.value.error as StringResource).resId,
)
assertTrue(viewModel.uiState.value.saving.isEmpty())
}
@Test
fun collectsRepositoryChanges() = runTest {
val repository = RecordingUiPreferencesRepository()
val viewModel = SettingsViewModel(repository)
advanceUntilIdle()
repository.emit(
UiPreferences(
renderer = DesignRenderer.MIUIX,
themeMode = AppThemeMode.DARK,
dynamicColorEnabled = false,
blurEnabled = true,
navigationPresentation = NavigationPresentation.FLOATING,
hapticsEnabled = false,
),
)
advanceUntilIdle()
assertTrue(viewModel.uiState.value.loaded)
assertEquals(repository.value, viewModel.uiState.value.preferences)
}
private class RecordingUiPreferencesRepository(
initial: UiPreferences = UiPreferences(),
private val writeResult: UiPreferencesWriteResult = UiPreferencesWriteResult.Success,
) : UiPreferencesRepository {
private val state = MutableStateFlow(initial)
val updateCalls = mutableListOf<(UiPreferences) -> UiPreferences>()
val value: UiPreferences get() = state.value
override fun observe(): Flow<UiPreferences> = state.asStateFlow()
override suspend fun update(
transform: (UiPreferences) -> UiPreferences,
): UiPreferencesWriteResult {
updateCalls += transform
if (writeResult is UiPreferencesWriteResult.Success) {
state.value = transform(state.value)
}
return writeResult
}
fun emit(value: UiPreferences) {
state.value = value
}
}
}