1package com.peachpasswords23import android.content.Intent4import android.graphics.Color as AndroidColor5import android.graphics.drawable.ColorDrawable6import android.os.Bundle7import androidx.activity.compose.setContent8import androidx.activity.enableEdgeToEdge9import androidx.compose.runtime.SideEffect10import androidx.compose.ui.unit.dp11import androidx.compose.ui.unit.Density12import androidx.compose.ui.platform.LocalDensity13import androidx.compose.ui.Modifier14import androidx.compose.runtime.CompositionLocalProvider15import androidx.compose.runtime.Composable16import androidx.compose.foundation.layout.fillMaxSize17import androidx.compose.foundation.layout.BoxWithConstraints18import androidx.compose.runtime.collectAsState19import androidx.compose.runtime.getValue20import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen21import androidx.core.view.WindowCompat22import androidx.fragment.app.FragmentActivity23import dagger.hilt.android.AndroidEntryPoint24import javax.inject.Inject25import com.peachpasswords.autofill.AutofillIntents26import com.peachpasswords.autofill.AutofillNavRepository27import com.peachpasswords.ui.PeachAppNavHost28import com.peachpasswords.ui.components.Favicons29import com.peachpasswords.ui.components.LocalFavicons30import com.peachpasswords.ui.components.LocalFaviconsEnabled31import com.peachpasswords.ui.components.QaFlags32import com.peachpasswords.ui.components.SecureWindowPolicy33import com.peachpasswords.ui.onboarding.ONBOARD_KEY34import com.peachpasswords.ui.onboarding.ONBOARD_PREFS35import com.peachpasswords.ui.onboarding.onboardingStartDestination36import com.peachpasswords.ui.theme.ACCENT_COLORS37import com.peachpasswords.ui.theme.DEFAULT_ACCENT38import com.peachpasswords.ui.theme.PeachTheme39import com.peachpasswords.updates.EXTRA_OPEN_UPDATE40import com.peachpasswords.updates.UpdateRepository41import com.peachpasswords.vault.VaultLockTimer42import com.peachpasswords.vault.VaultRepository434445@AndroidEntryPoint46class MainActivity : FragmentActivity() {47 @Inject lateinit var vault: VaultRepository48 @Inject lateinit var favicons: Favicons49 @Inject lateinit var lockTimer: VaultLockTimer50 @Inject lateinit var updates: UpdateRepository51 @Inject lateinit var autofillNav: AutofillNavRepository5253 54 override fun onUserInteraction() {55 super.onUserInteraction()56 lockTimer.poke()57 }5859 60 61 62 override fun onNewIntent(intent: Intent) {63 super.onNewIntent(intent)64 setIntent(intent)65 routeIntent(intent)66 if (BuildConfig.DEBUG) {67 QaFlags.disableSecureCaptureForQa = intent.getBooleanExtra(68 QaFlags.EXTRA_DISABLE_SECURE_CAPTURE,69 false,70 )71 }72 }7374 override fun onCreate(savedInstanceState: Bundle?) {75 installSplashScreen()76 super.onCreate(savedInstanceState)7778 79 routeIntent(intent)8081 82 83 window.setBackgroundDrawable(ColorDrawable(AndroidColor.parseColor("#0C0A06")))8485 enableEdgeToEdge()8687 88 89 90 if (!BuildConfig.DEBUG) {91 SecureWindowPolicy.setUserSecure(window, true)92 } else {93 QaFlags.disableSecureCaptureForQa = intent.getBooleanExtra(94 QaFlags.EXTRA_DISABLE_SECURE_CAPTURE,95 false,96 )97 }9899 100 val onboarded = getSharedPreferences(ONBOARD_PREFS, MODE_PRIVATE)101 .getBoolean(ONBOARD_KEY, false)102 val start = onboardingStartDestination(hasVault = vault.hasVault(), onboarded = onboarded)103104 setContent {105 val settings by vault.settings.collectAsState()106 107 108 109 110 SideEffect {111 WindowCompat.getInsetsController(window, window.decorView).apply {112 isAppearanceLightStatusBars = false113 isAppearanceLightNavigationBars = false114 }115 }116 val accent = ACCENT_COLORS[settings.accentColor] ?: DEFAULT_ACCENT117 SideEffect {118 if (!BuildConfig.DEBUG) {119 SecureWindowPolicy.setUserSecure(window, !settings.allowScreenshots)120 }121 }122 CompositionLocalProvider(123 LocalFavicons provides favicons,124 LocalFaviconsEnabled provides settings.faviconsEnabled,125 ) {126 PeachTheme(127 accent = accent,128 reduceMotion = settings.reduceMotion,129 textSize = settings.textSize,130 ) {131 WideScreenScale {132 PeachAppNavHost(startDestination = start, isUnlocked = vault.isUnlocked, uiGateLocked = vault.uiGateLocked)133 }134 }135 }136 }137 }138}139140141private val BASE_WIDTH = 411.dp142143144145146147148149150151152private fun MainActivity.routeIntent(intent: Intent) {153 if (intent.getBooleanExtra(EXTRA_OPEN_UPDATE, false)) updates.requestOpen()154 forwardNewEntryRequest(intent)155 routeAction(intent.action)?.let(PendingActionQueue::request)156}157158159160161162163private fun MainActivity.forwardNewEntryRequest(intent: Intent) {164 if (!intent.getBooleanExtra(AutofillIntents.EXTRA_OPEN_NEW_ENTRY, false)) return165 166 167 if (!AutofillIntents.consumeNewEntryToken(intent.getStringExtra(AutofillIntents.EXTRA_NEW_ENTRY_TOKEN))) return168 autofillNav.requestNewEntry(169 url = intent.getStringExtra(AutofillIntents.EXTRA_NEW_ENTRY_URL),170 username = intent.getStringExtra(AutofillIntents.EXTRA_NEW_ENTRY_USERNAME),171 email = intent.getStringExtra(AutofillIntents.EXTRA_NEW_ENTRY_EMAIL),172 )173}174175176private const val MAX_SCALE = 1.35f177178179180181182183184185186187188@Composable189private fun WideScreenScale(content: @Composable () -> Unit) {190 BoxWithConstraints(Modifier.fillMaxSize()) {191 val base = LocalDensity.current192 val scale = (maxWidth / BASE_WIDTH).coerceIn(1f, MAX_SCALE)193 if (scale == 1f) {194 content()195 } else {196 CompositionLocalProvider(197 LocalDensity provides Density(base.density * scale, base.fontScale),198 content = content,199 )200 }201 }202}