Read the theme, locale, empty-library seed and section expansion once in the application and collect them as state, instead of blocking the UI thread with synchronous reads before setContent.
154 lines
6.1 KiB
Kotlin
154 lines
6.1 KiB
Kotlin
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
|
|
|
|
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 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,
|
|
app.initialLaunch.value?.sectionExpansion ?: SectionExpansionStates(
|
|
isThemeExpanded = false,
|
|
isLanguageExpanded = false,
|
|
isDictionariesExpanded = true,
|
|
),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
enableEdgeToEdge()
|
|
super.onCreate(savedInstanceState)
|
|
requestNotificationPermissionIfNeeded()
|
|
|
|
val app = application as ReSearchApplication
|
|
handleIntent(intent)
|
|
setContent {
|
|
val initialLaunch by app.initialLaunch.collectAsStateWithLifecycle()
|
|
val launch = initialLaunch ?: return@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 } ?: launch.theme
|
|
val language = settingsState.language.takeIf { it != "system" } ?: launch.language
|
|
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 = launch.seedNoDictionaries
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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))
|
|
}
|
|
}
|
|
}
|
|
}
|