package com.example.research import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.core.content.ContextCompat import androidx.core.content.edit import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.createSavedStateHandle import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory 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 import com.example.research.ui.settings.SettingsViewModel import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking class MainActivity : ComponentActivity() { companion object { private const val PREF_NOTIFICATION_PERMISSION_REQUESTED = "notification_permission_requested" } private val notificationPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { /* Permission result handled silently */ } private val searchViewModel: SearchViewModel by viewModels { val app = application as ReSearchApplication viewModelFactory { initializer { SearchViewModel(application, app.localDictionaryRepository, createSavedStateHandle()) } } } private var initialSectionExpansion: SectionExpansionStates? = null private val settingsViewModel: SettingsViewModel by viewModels { val app = application as ReSearchApplication val versionName = try { packageManager.getPackageInfo(packageName, 0).versionName ?: "1.0" } catch (_: Exception) { "1.0" } viewModelFactory { initializer { SettingsViewModel( application, app.preferencesManager, app.downloadDictionaryRepository, app.downloadManager, app.localDictionaryRepository, app.dictionaryImportManager, app.dictionaryProgressStateHolder, versionName, initialSectionExpansion ?: SectionExpansionStates( isThemeExpanded = false, isLanguageExpanded = false, isDictionariesExpanded = true, ), ) } } } override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) requestNotificationPermissionIfNeeded() // Load theme and language synchronously before setContent to prevent flashing val app = application as ReSearchApplication val initialTheme = loadInitialTheme(app) val initialLanguage = loadInitialLanguage(app) val initialHadNoDictionaries = loadInitialHadNoDictionaries(app) initialSectionExpansion = loadInitialSectionExpansion(app) handleIntent(intent) setContent { val settingsState by settingsViewModel.uiState.collectAsStateWithLifecycle() // Use loaded values initially, then switch to state values when ready val theme = settingsState.theme.takeIf { it != AppTheme.SYSTEM } ?: initialTheme val language = settingsState.language.takeIf { it != "system" } ?: initialLanguage val darkTheme = when (theme) { AppTheme.LIGHT -> false AppTheme.DARK -> true AppTheme.SYSTEM -> isSystemInDarkTheme() } AppLocaleProvider(language = language) { ReSearchTheme(darkTheme = darkTheme) { BackgroundTextMeasurementProvider { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { AppNavigation( searchViewModel = searchViewModel, settingsViewModel = settingsViewModel, seedNoDictionaries = initialHadNoDictionaries ) } } } } } } private fun requestNotificationPermissionIfNeeded() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return val permission = Manifest.permission.POST_NOTIFICATIONS val isGranted = ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED val prefs = getPreferences(MODE_PRIVATE) val wasRequested = prefs.getBoolean(PREF_NOTIFICATION_PERMISSION_REQUESTED, false) if (!isGranted && !wasRequested) { prefs.edit { putBoolean(PREF_NOTIFICATION_PERMISSION_REQUESTED, true) } notificationPermissionLauncher.launch(permission) } } /** * Load initial theme synchronously from DataStore to prevent theme flashing. * This is called before setContent to ensure the correct theme is applied immediately. * Uses runBlocking to synchronously read from DataStore. */ private fun loadInitialTheme(app: ReSearchApplication): AppTheme { return try { runBlocking { val themeString = app.preferencesManager.themeMode.first() try { AppTheme.valueOf(themeString) } catch (_: Exception) { android.util.Log.w("MainActivity", "Invalid theme value: $themeString, using SYSTEM") AppTheme.SYSTEM } } } catch (e: Exception) { android.util.Log.e("MainActivity", "Failed to load theme preference: ${e.message}") AppTheme.SYSTEM } } /** * Load initial language synchronously from DataStore to prevent language flashing. * This is called before setContent to ensure the correct language is applied immediately. * Uses runBlocking to synchronously read from DataStore. */ private fun loadInitialLanguage(app: ReSearchApplication): String { return try { runBlocking { app.preferencesManager.language.first() } } catch (e: Exception) { android.util.Log.e("MainActivity", "Failed to load language preference: ${e.message}") "system" } } 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) } override fun onUserLeaveHint() { super.onUserLeaveHint() // Debug hook for user leaving the app } private fun handleIntent(intent: Intent) { if (intent.action == Intent.ACTION_PROCESS_TEXT) { val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT) ?: intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT_READONLY) text?.toString()?.let { searchViewModel.onAction(SearchAction.HandleExternalSearch(it)) } } } }