From d96fad9cab432bc9b82b0d7b41cc5d4aad7c19df Mon Sep 17 00:00:00 2001 From: OneWay Date: Fri, 4 Sep 2026 19:09:19 +0800 Subject: [PATCH] fix: open Settings immediately on cold start when no dictionaries Seed and synchronously read hadNoDictionaries before setContent so Settings shows from the first frame until the startup scan confirms. --- .../java/com/example/research/MainActivity.kt | 38 ++++++++++++++++++- .../local/preferences/PreferencesManager.kt | 28 ++++++++++++++ .../research/ui/navigation/AppNavigation.kt | 6 ++- .../research/ui/settings/SettingsViewModel.kt | 25 +++++++++++- 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/research/MainActivity.kt b/app/src/main/java/com/example/research/MainActivity.kt index 6941f54..af24008 100644 --- a/app/src/main/java/com/example/research/MainActivity.kt +++ b/app/src/main/java/com/example/research/MainActivity.kt @@ -26,6 +26,7 @@ import com.example.research.common.ui.AppLocaleProvider import com.example.research.common.ui.BackgroundTextMeasurementProvider import com.example.research.common.ui.theme.ReSearchTheme import com.example.research.core.domain.model.AppTheme +import com.example.research.data.local.preferences.SectionExpansionStates import com.example.research.feature.search.SearchAction import com.example.research.feature.search.SearchViewModel import com.example.research.ui.navigation.AppNavigation @@ -51,6 +52,8 @@ class MainActivity : ComponentActivity() { } } + private var initialSectionExpansion: SectionExpansionStates? = null + private val settingsViewModel: SettingsViewModel by viewModels { val app = application as ReSearchApplication val versionName = try { @@ -69,6 +72,11 @@ class MainActivity : ComponentActivity() { app.dictionaryImportManager, app.dictionaryProgressStateHolder, versionName, + initialSectionExpansion ?: SectionExpansionStates( + isThemeExpanded = false, + isLanguageExpanded = false, + isDictionariesExpanded = true, + ), ) } } @@ -83,6 +91,8 @@ class MainActivity : ComponentActivity() { val app = application as ReSearchApplication val initialTheme = loadInitialTheme(app) val initialLanguage = loadInitialLanguage(app) + val initialHadNoDictionaries = loadInitialHadNoDictionaries(app) + initialSectionExpansion = loadInitialSectionExpansion(app) handleIntent(intent) setContent { @@ -104,7 +114,8 @@ class MainActivity : ComponentActivity() { ) { AppNavigation( searchViewModel = searchViewModel, - settingsViewModel = settingsViewModel + settingsViewModel = settingsViewModel, + seedNoDictionaries = initialHadNoDictionaries ) } } @@ -165,6 +176,31 @@ class MainActivity : ComponentActivity() { } } + private fun loadInitialHadNoDictionaries(app: ReSearchApplication): Boolean { + return try { + runBlocking { + app.preferencesManager.hadNoDictionaries.first() + } + } catch (e: Exception) { + true + } + } + + private fun loadInitialSectionExpansion(app: ReSearchApplication): SectionExpansionStates { + return try { + runBlocking { + app.preferencesManager.sectionExpansionStates() + } + } catch (e: Exception) { + android.util.Log.e("MainActivity", "Failed to load section expansion states: ${e.message}") + SectionExpansionStates( + isThemeExpanded = false, + isLanguageExpanded = false, + isDictionariesExpanded = true, + ) + } + } + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) handleIntent(intent) diff --git a/app/src/main/java/com/example/research/data/local/preferences/PreferencesManager.kt b/app/src/main/java/com/example/research/data/local/preferences/PreferencesManager.kt index 7896611..7981f4f 100644 --- a/app/src/main/java/com/example/research/data/local/preferences/PreferencesManager.kt +++ b/app/src/main/java/com/example/research/data/local/preferences/PreferencesManager.kt @@ -10,12 +10,19 @@ import androidx.datastore.preferences.core.stringSetPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.example.research.core.domain.model.DictionarySource import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.serialization.json.Json import java.io.File private val Context.dataStore: DataStore by preferencesDataStore(name = "settings") +data class SectionExpansionStates( + val isThemeExpanded: Boolean, + val isLanguageExpanded: Boolean, + val isDictionariesExpanded: Boolean, +) + class PreferencesManager(private val context: Context) { private object PreferencesKeys { val THEME_MODE = stringPreferencesKey("theme_mode") @@ -25,6 +32,7 @@ class PreferencesManager(private val context: Context) { val IS_LANGUAGE_EXPANDED = booleanPreferencesKey("is_language_expanded") val IS_DICTIONARIES_EXPANDED = booleanPreferencesKey("is_dictionaries_expanded") val DISABLED_DICTIONARY_PATHS = stringSetPreferencesKey("disabled_dictionary_paths") + val HAD_NO_DICTIONARIES = booleanPreferencesKey("had_no_dictionaries") } private val json = Json { ignoreUnknownKeys = true } @@ -67,6 +75,26 @@ class PreferencesManager(private val context: Context) { preferences[PreferencesKeys.DISABLED_DICTIONARY_PATHS] ?: emptySet() } + val hadNoDictionaries: Flow = context.dataStore.data + .map { preferences -> + preferences[PreferencesKeys.HAD_NO_DICTIONARIES] ?: true + } + + suspend fun setHadNoDictionaries(value: Boolean) { + context.dataStore.edit { preferences -> + preferences[PreferencesKeys.HAD_NO_DICTIONARIES] = value + } + } + + suspend fun sectionExpansionStates(): SectionExpansionStates = + context.dataStore.data.first().let { preferences -> + SectionExpansionStates( + isThemeExpanded = preferences[PreferencesKeys.IS_THEME_EXPANDED] ?: false, + isLanguageExpanded = preferences[PreferencesKeys.IS_LANGUAGE_EXPANDED] ?: false, + isDictionariesExpanded = preferences[PreferencesKeys.IS_DICTIONARIES_EXPANDED] ?: true, + ) + } + suspend fun setDictionaryActive(path: String, isActive: Boolean) { context.dataStore.edit { preferences -> val disabledPaths = preferences[PreferencesKeys.DISABLED_DICTIONARY_PATHS] diff --git a/app/src/main/java/com/example/research/ui/navigation/AppNavigation.kt b/app/src/main/java/com/example/research/ui/navigation/AppNavigation.kt index a59f857..67ff337 100644 --- a/app/src/main/java/com/example/research/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/example/research/ui/navigation/AppNavigation.kt @@ -33,7 +33,8 @@ enum class Screen { @Composable fun AppNavigation( searchViewModel: SearchViewModel, - settingsViewModel: SettingsViewModel + settingsViewModel: SettingsViewModel, + seedNoDictionaries: Boolean = false ) { val screenStack = rememberSaveable( saver = listSaver( @@ -84,8 +85,9 @@ fun AppNavigation( val isOperationActive = stickyPipelineActive val hasConfirmedNoDictionaries = settingsState.hasCompletedStartupScan && settingsState.dictionaries.isEmpty() + val hasSeededNoDictionaries = seedNoDictionaries && !settingsState.hasCompletedStartupScan NavigationState( - shouldShowSettings = hasConfirmedNoDictionaries || isOperationActive, + shouldShowSettings = hasConfirmedNoDictionaries || hasSeededNoDictionaries || isOperationActive, showBackButtonInSettings = settingsState.dictionaries.isNotEmpty() && !isOperationActive, isOperationActive = isOperationActive, ) diff --git a/app/src/main/java/com/example/research/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/example/research/ui/settings/SettingsViewModel.kt index e90164d..99e4025 100644 --- a/app/src/main/java/com/example/research/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/example/research/ui/settings/SettingsViewModel.kt @@ -16,6 +16,7 @@ import com.example.research.core.domain.usecase.DictionarySourceValidator import com.example.research.core.domain.usecase.ManageDictionarySourcesUseCase import com.example.research.core.util.OperationResult import com.example.research.data.local.preferences.PreferencesManager +import com.example.research.data.local.preferences.SectionExpansionStates import com.example.research.data.repository.LocalDictionaryRepository import com.example.research.feature.download.DownloadManager import com.example.research.feature.download.model.DownloadState @@ -60,6 +61,7 @@ class SettingsViewModel( private val dictionaryImportManager: DictionaryImportManager, progressStateHolder: DictionaryProgressStateHolder, appVersion: String, + initialSectionExpansion: SectionExpansionStates, ) : AndroidViewModel(application) { private val manageDictionarySourcesUseCase = ManageDictionarySourcesUseCase( @@ -67,7 +69,14 @@ class SettingsViewModel( validator = DictionarySourceValidator(), ) - private val _uiState = MutableStateFlow(SettingsUiState(appVersion = appVersion)) + private val _uiState = MutableStateFlow( + SettingsUiState( + appVersion = appVersion, + isThemeExpanded = initialSectionExpansion.isThemeExpanded, + isLanguageExpanded = initialSectionExpansion.isLanguageExpanded, + isDictionariesExpanded = initialSectionExpansion.isDictionariesExpanded, + ) + ) val uiState: StateFlow = _uiState.asStateFlow() val progressSnapshot = progressStateHolder.progressSnapshot @@ -89,9 +98,20 @@ class SettingsViewModel( init { setupStateObservation() + observeDictionariesForSeedFlag() initialize() } + private fun observeDictionariesForSeedFlag() { + viewModelScope.launch { + localDictionaryRepository.dictionaries.collect { dictionaries -> + if (hasCompletedStartupScan.value) { + preferencesManager.setHadNoDictionaries(dictionaries.isEmpty()) + } + } + } + } + private fun setupStateObservation() { viewModelScope.launch { var wasIndexing = false @@ -245,6 +265,9 @@ class SettingsViewModel( android.util.Log.w("SettingsViewModel", "Failed to load language preference: ${e.message}") } hasCompletedStartupScan.value = true + preferencesManager.setHadNoDictionaries( + localDictionaryRepository.dictionaries.value.isEmpty() + ) } } }