1package com.peachpasswords23import android.content.Intent4import android.content.res.Configuration5import android.graphics.Color as AndroidColor6import android.graphics.drawable.ColorDrawable7import android.os.Bundle8import android.view.WindowManager9import androidx.activity.compose.setContent10import androidx.activity.enableEdgeToEdge11import androidx.compose.foundation.isSystemInDarkTheme12import androidx.compose.runtime.SideEffect13import androidx.compose.ui.unit.dp14import androidx.compose.ui.unit.Density15import androidx.compose.ui.platform.LocalDensity16import androidx.compose.ui.Modifier17import androidx.compose.runtime.CompositionLocalProvider18import androidx.compose.runtime.Composable19import androidx.compose.foundation.layout.fillMaxSize20import androidx.compose.foundation.layout.BoxWithConstraints21import androidx.compose.runtime.collectAsState22import androidx.compose.runtime.getValue23import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen24import androidx.core.view.WindowCompat25import androidx.fragment.app.FragmentActivity26import dagger.hilt.android.AndroidEntryPoint27import javax.inject.Inject28import com.peachpasswords.autofill.AutofillIntents29import com.peachpasswords.autofill.AutofillNavRepository30import com.peachpasswords.ui.PeachAppNavHost31import com.peachpasswords.ui.components.QaFlags32import com.peachpasswords.ui.onboarding.ONBOARD_KEY33import com.peachpasswords.ui.onboarding.ONBOARD_PREFS34import com.peachpasswords.ui.onboarding.onboardingStartDestination35import com.peachpasswords.ui.theme.ACCENT_COLORS36import com.peachpasswords.ui.theme.DEFAULT_ACCENT37import com.peachpasswords.ui.theme.PeachTheme38import com.peachpasswords.updates.EXTRA_OPEN_UPDATE39import com.peachpasswords.updates.UpdateRepository40import com.peachpasswords.vault.VaultLockTimer41import com.peachpasswords.vault.VaultRepository424344@AndroidEntryPoint45class MainActivity : FragmentActivity() {46 @Inject lateinit var vault: VaultRepository47 @Inject lateinit var lockTimer: VaultLockTimer48 @Inject lateinit var updates: UpdateRepository49 @Inject lateinit var autofillNav: AutofillNavRepository5051 52 override fun onUserInteraction() {53 super.onUserInteraction()54 lockTimer.poke()55 }5657 58 59 60 override fun onNewIntent(intent: Intent) {61 super.onNewIntent(intent)62 setIntent(intent)63 if (intent.getBooleanExtra(EXTRA_OPEN_UPDATE, false)) updates.requestOpen()64 forwardNewEntryRequest(intent)65 if (BuildConfig.DEBUG) {66 QaFlags.disableSecureCaptureForQa = intent.getBooleanExtra("qa_disable_secure_flag", false)67 }68 }6970 override fun onCreate(savedInstanceState: Bundle?) {71 installSplashScreen()72 super.onCreate(savedInstanceState)7374 75 if (intent.getBooleanExtra(EXTRA_OPEN_UPDATE, false)) updates.requestOpen()76 forwardNewEntryRequest(intent)7778 79 80 81 val initialTheme = vault.settings.value.theme82 val initialDark = when (initialTheme) {83 "light" -> false84 "system" -> (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==85 Configuration.UI_MODE_NIGHT_YES86 else -> true87 }88 window.setBackgroundDrawable(89 ColorDrawable(AndroidColor.parseColor(if (initialDark) "#0C0A06" else "#EFEEE9")),90 )9192 enableEdgeToEdge()9394 95 if (!BuildConfig.DEBUG) {96 window.setFlags(97 WindowManager.LayoutParams.FLAG_SECURE,98 WindowManager.LayoutParams.FLAG_SECURE,99 )100 } else {101 102 103 104 QaFlags.disableSecureCaptureForQa = intent.getBooleanExtra("qa_disable_secure_flag", false)105 }106107 108 val onboarded = getSharedPreferences(ONBOARD_PREFS, MODE_PRIVATE)109 .getBoolean(ONBOARD_KEY, false)110 val start = onboardingStartDestination(hasVault = vault.hasVault(), onboarded = onboarded)111112 setContent {113 val settings by vault.settings.collectAsState()114 val darkTheme = when (settings.theme) {115 "light" -> false116 "system" -> isSystemInDarkTheme()117 else -> true118 }119 120 121 122 123 124 125 126 127 SideEffect {128 WindowCompat.getInsetsController(window, window.decorView).apply {129 isAppearanceLightStatusBars = !darkTheme130 isAppearanceLightNavigationBars = !darkTheme131 }132 }133 val accent = ACCENT_COLORS[settings.accentColor] ?: DEFAULT_ACCENT134 PeachTheme(135 darkTheme = darkTheme,136 accent = accent,137 reduceMotion = settings.reduceMotion,138 ) {139 WideScreenScale {140 PeachAppNavHost(startDestination = start, isUnlocked = vault.isUnlocked)141 }142 }143 }144 }145}146147148private val BASE_WIDTH = 411.dp149150151152153154155private fun MainActivity.forwardNewEntryRequest(intent: Intent) {156 if (!intent.getBooleanExtra(AutofillIntents.EXTRA_OPEN_NEW_ENTRY, false)) return157 autofillNav.requestNewEntry(158 url = intent.getStringExtra(AutofillIntents.EXTRA_NEW_ENTRY_URL),159 username = intent.getStringExtra(AutofillIntents.EXTRA_NEW_ENTRY_USERNAME),160 email = intent.getStringExtra(AutofillIntents.EXTRA_NEW_ENTRY_EMAIL),161 )162}163164165private const val MAX_SCALE = 1.35f166167168169170171172173174175176177@Composable178private fun WideScreenScale(content: @Composable () -> Unit) {179 BoxWithConstraints(Modifier.fillMaxSize()) {180 val base = LocalDensity.current181 val scale = (maxWidth / BASE_WIDTH).coerceIn(1f, MAX_SCALE)182 if (scale == 1f) {183 content()184 } else {185 CompositionLocalProvider(186 LocalDensity provides Density(base.density * scale, base.fontScale),187 content = content,188 )189 }190 }191}