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.QaFlags31import com.peachpasswords.ui.components.SecureWindowPolicy32import 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 favicons: Favicons48 @Inject lateinit var lockTimer: VaultLockTimer49 @Inject lateinit var updates: UpdateRepository50 @Inject lateinit var autofillNav: AutofillNavRepository5152 53 override fun onUserInteraction() {54 super.onUserInteraction()55 lockTimer.poke()56 }5758 59 60 61 override fun onNewIntent(intent: Intent) {62 super.onNewIntent(intent)63 setIntent(intent)64 routeIntent(intent)65 if (BuildConfig.DEBUG) {66 QaFlags.disableSecureCaptureForQa = intent.getBooleanExtra(67 QaFlags.EXTRA_DISABLE_SECURE_CAPTURE,68 false,69 )70 }71 }7273 override fun onCreate(savedInstanceState: Bundle?) {74 installSplashScreen()75 super.onCreate(savedInstanceState)7677 78 routeIntent(intent)7980 81 82 window.setBackgroundDrawable(ColorDrawable(AndroidColor.parseColor("#0C0A06")))8384 enableEdgeToEdge()8586 87 88 89 if (!BuildConfig.DEBUG) {90 SecureWindowPolicy.setUserSecure(window, true)91 } else {92 QaFlags.disableSecureCaptureForQa = intent.getBooleanExtra(93 QaFlags.EXTRA_DISABLE_SECURE_CAPTURE,94 false,95 )96 }9798 99 val onboarded = getSharedPreferences(ONBOARD_PREFS, MODE_PRIVATE)100 .getBoolean(ONBOARD_KEY, false)101 val start = onboardingStartDestination(hasVault = vault.hasVault(), onboarded = onboarded)102103 setContent {104 val settings by vault.settings.collectAsState()105 106 107 108 109 SideEffect {110 WindowCompat.getInsetsController(window, window.decorView).apply {111 isAppearanceLightStatusBars = false112 isAppearanceLightNavigationBars = false113 }114 }115 val accent = ACCENT_COLORS[settings.accentColor] ?: DEFAULT_ACCENT116 SideEffect {117 if (!BuildConfig.DEBUG) {118 SecureWindowPolicy.setUserSecure(window, !settings.allowScreenshots)119 }120 }121 CompositionLocalProvider(LocalFavicons provides favicons) {122 PeachTheme(123 accent = accent,124 reduceMotion = settings.reduceMotion,125 textSize = settings.textSize,126 ) {127 WideScreenScale {128 PeachAppNavHost(startDestination = start, isUnlocked = vault.isUnlocked, uiGateLocked = vault.uiGateLocked)129 }130 }131 }132 }133 }134}135136137private val BASE_WIDTH = 411.dp138139140141142143144145146147148private fun MainActivity.routeIntent(intent: Intent) {149 if (intent.getBooleanExtra(EXTRA_OPEN_UPDATE, false)) updates.requestOpen()150 forwardNewEntryRequest(intent)151 routeAction(intent.action)?.let(PendingActionQueue::request)152}153154155156157158159private fun MainActivity.forwardNewEntryRequest(intent: Intent) {160 if (!intent.getBooleanExtra(AutofillIntents.EXTRA_OPEN_NEW_ENTRY, false)) return161 autofillNav.requestNewEntry(162 url = intent.getStringExtra(AutofillIntents.EXTRA_NEW_ENTRY_URL),163 username = intent.getStringExtra(AutofillIntents.EXTRA_NEW_ENTRY_USERNAME),164 email = intent.getStringExtra(AutofillIntents.EXTRA_NEW_ENTRY_EMAIL),165 )166}167168169private const val MAX_SCALE = 1.35f170171172173174175176177178179180181@Composable182private fun WideScreenScale(content: @Composable () -> Unit) {183 BoxWithConstraints(Modifier.fillMaxSize()) {184 val base = LocalDensity.current185 val scale = (maxWidth / BASE_WIDTH).coerceIn(1f, MAX_SCALE)186 if (scale == 1f) {187 content()188 } else {189 CompositionLocalProvider(190 LocalDensity provides Density(base.density * scale, base.fontScale),191 content = content,192 )193 }194 }195}