diff --git a/app/src/main/java/org/searchmob/ui/settings/SettingsViewModel.kt b/app/src/main/java/org/searchmob/ui/settings/SettingsViewModel.kt index 1c46fa4..6cc402b 100644 --- a/app/src/main/java/org/searchmob/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/org/searchmob/ui/settings/SettingsViewModel.kt @@ -227,14 +227,28 @@ class SettingsViewModel( viewModelScope.launch { preferences.setDynamicColor(enabled) } } - /** Set the named theme for the light slot (light-mode themes only). Persists immediately. */ + /** + * Set the named theme for the light slot (light-mode themes only). Persists immediately. + * Picking a theme is an explicit choice to use it, so Material You (which overrides the named + * themes while it is on) is switched off in the same edit; otherwise the pick appears to do + * nothing until the user finds the toggle. + */ fun setLightTheme(themeId: String) { - viewModelScope.launch { preferences.setLightTheme(themeId) } + viewModelScope.launch { + preferences.setDynamicColor(false) + preferences.setLightTheme(themeId) + } } - /** Set the named theme for the dark slot (dark-mode themes only). Persists immediately. */ + /** + * Set the named theme for the dark slot (dark-mode themes only). Persists immediately. + * Switches Material You off for the same reason as [setLightTheme]. + */ fun setDarkTheme(themeId: String) { - viewModelScope.launch { preferences.setDarkTheme(themeId) } + viewModelScope.launch { + preferences.setDynamicColor(false) + preferences.setDarkTheme(themeId) + } } /** Set the base UI font size in points; clamped to the supported range by the repository. */ diff --git a/app/src/test/java/org/searchmob/ui/settings/SettingsViewModelThemeTest.kt b/app/src/test/java/org/searchmob/ui/settings/SettingsViewModelThemeTest.kt new file mode 100644 index 0000000..9804d1a --- /dev/null +++ b/app/src/test/java/org/searchmob/ui/settings/SettingsViewModelThemeTest.kt @@ -0,0 +1,83 @@ +package org.searchmob.ui.settings + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.searchmob.data.history.InMemoryHistoryStore +import org.searchmob.ui.prefs.InMemoryPreferencesStore +import org.searchmob.ui.prefs.PreferencesRepository + +@OptIn(ExperimentalCoroutinesApi::class) +class SettingsViewModelThemeTest { + private val dispatcher = StandardTestDispatcher() + private lateinit var preferences: PreferencesRepository + private lateinit var viewModel: SettingsViewModel + + @Before + fun setUp() { + Dispatchers.setMain(dispatcher) + preferences = PreferencesRepository(InMemoryPreferencesStore()) + viewModel = + SettingsViewModel( + preferences = preferences, + historyStore = InMemoryHistoryStore(), + engineCatalog = emptyList(), + engineEnabledSink = MutableStateFlow(emptyMap()), + apiKeysSink = MutableStateFlow(emptyMap()), + ) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + } + + @Test + fun `picking a light theme switches Material You off so the pick takes effect`() = + runTest(dispatcher) { + assertTrue(preferences.preferences.first().dynamicColor) // default on + + viewModel.setLightTheme("github-light") + advanceUntilIdle() + + val prefs = preferences.preferences.first() + assertEquals("github-light", prefs.lightThemeId) + assertFalse(prefs.dynamicColor) + } + + @Test + fun `picking a dark theme switches Material You off so the pick takes effect`() = + runTest(dispatcher) { + viewModel.setDarkTheme("dracula") + advanceUntilIdle() + + val prefs = preferences.preferences.first() + assertEquals("dracula", prefs.darkThemeId) + assertFalse(prefs.dynamicColor) + } + + @Test + fun `re-enabling Material You after a pick is respected`() = + runTest(dispatcher) { + viewModel.setDarkTheme("dracula") + advanceUntilIdle() + viewModel.setDynamicColor(true) + advanceUntilIdle() + + val prefs = preferences.preferences.first() + assertTrue(prefs.dynamicColor) + assertEquals("dracula", prefs.darkThemeId) // the slot choice is kept + } +}