From d8d3be374d69d0c0fcbec7c15995e74f2096c766 Mon Sep 17 00:00:00 2001 From: FlintWave Date: Thu, 2 Jul 2026 18:04:03 -0700 Subject: [PATCH] fix(settings): picking a theme switches Material You off so the pick applies The theme dropdowns persisted the choice but Material You (on by default) kept overriding it, so on most devices picking a theme appeared to do nothing; the only clue was the hint sentence below the dropdowns. Choosing a named theme is an explicit statement of intent, so the pick now switches dynamic color off in the same edit and takes effect immediately. Re-enabling Material You afterwards still works and keeps the slot choice. Co-Authored-By: Claude Fable 5 --- .../ui/settings/SettingsViewModel.kt | 22 ++++- .../ui/settings/SettingsViewModelThemeTest.kt | 83 +++++++++++++++++++ 2 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 app/src/test/java/org/searchmob/ui/settings/SettingsViewModelThemeTest.kt 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 + } +}