secant-android-wallet/ui-lib/src/main/java/cash/z/ecc/ui/MainActivity.kt

215 lines
8.3 KiB
Kotlin
Raw Normal View History

2021-10-09 07:36:58 -07:00
package cash.z.ecc.ui
2021-12-02 12:33:55 -08:00
import android.content.ClipData
import android.content.ClipboardManager
2021-12-06 12:31:39 -08:00
import android.content.Context
import android.os.Bundle
2021-12-03 05:19:15 -08:00
import android.os.SystemClock
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
2021-12-03 05:19:15 -08:00
import androidx.annotation.VisibleForTesting
2021-12-09 12:18:18 -08:00
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
2021-12-09 12:18:18 -08:00
import androidx.compose.ui.Modifier
2021-12-06 12:31:39 -08:00
import androidx.core.content.res.ResourcesCompat
2021-12-03 05:19:15 -08:00
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
2021-12-06 12:31:39 -08:00
import androidx.lifecycle.lifecycleScope
2021-12-09 12:21:30 -08:00
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.sdk.model.PersistableWallet
2021-12-09 12:21:30 -08:00
import cash.z.ecc.sdk.model.SeedPhrase
import cash.z.ecc.sdk.type.fromResources
import cash.z.ecc.ui.screen.backup.view.BackupWallet
import cash.z.ecc.ui.screen.backup.viewmodel.BackupViewModel
2021-12-09 12:18:18 -08:00
import cash.z.ecc.ui.screen.common.GradientSurface
import cash.z.ecc.ui.screen.home.view.Home
import cash.z.ecc.ui.screen.home.viewmodel.WalletState
import cash.z.ecc.ui.screen.home.viewmodel.WalletViewModel
import cash.z.ecc.ui.screen.onboarding.view.Onboarding
import cash.z.ecc.ui.screen.onboarding.viewmodel.OnboardingViewModel
2021-12-09 12:21:30 -08:00
import cash.z.ecc.ui.screen.restore.view.RestoreWallet
import cash.z.ecc.ui.screen.restore.viewmodel.CompleteWordSetState
import cash.z.ecc.ui.screen.restore.viewmodel.RestoreViewModel
import cash.z.ecc.ui.theme.ZcashTheme
2021-12-06 12:31:39 -08:00
import cash.z.ecc.ui.util.AndroidApiVersion
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
2021-12-03 05:19:15 -08:00
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() {
private val walletViewModel by viewModels<WalletViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
2021-12-03 05:19:15 -08:00
setupSplashScreen()
2021-12-06 12:31:39 -08:00
if (AndroidApiVersion.isAtLeastO) {
setupUiContent()
} else {
lifecycleScope.launch {
prefetchFontLegacy(applicationContext, R.font.rubik_medium)
prefetchFontLegacy(applicationContext, R.font.rubik_regular)
2021-12-03 05:19:15 -08:00
2021-12-06 12:31:39 -08:00
setupUiContent()
}
}
}
2021-12-03 05:19:15 -08:00
private fun setupSplashScreen() {
2021-12-09 12:18:18 -08:00
val splashScreen = installSplashScreen()
val start = SystemClock.elapsedRealtime().milliseconds
splashScreen.setKeepVisibleCondition {
if (SPLASH_SCREEN_DELAY > Duration.ZERO) {
val now = SystemClock.elapsedRealtime().milliseconds
2021-12-03 05:19:15 -08:00
2021-12-09 12:18:18 -08:00
// This delay is for debug purposes only; do not enable for production usage.
if (now - start < SPLASH_SCREEN_DELAY) {
return@setKeepVisibleCondition true
}
2021-12-03 05:19:15 -08:00
}
2021-12-09 12:18:18 -08:00
WalletState.Loading == walletViewModel.state.value
2021-12-03 05:19:15 -08:00
}
}
2021-12-06 12:31:39 -08:00
private fun setupUiContent() {
setContent {
ZcashTheme {
2021-12-09 12:18:18 -08:00
GradientSurface(
Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
val walletState = walletViewModel.state.collectAsState().value
when (walletState) {
WalletState.Loading -> {
// For now, keep displaying splash screen using condition above.
// In the future, we might consider displaying something different here.
}
WalletState.NoWallet -> {
WrapOnboarding()
}
is WalletState.NeedsBackup -> WrapBackup(walletState.persistableWallet)
is WalletState.Ready -> WrapHome(walletState.persistableWallet)
2021-12-06 12:31:39 -08:00
}
2021-12-09 12:18:18 -08:00
if (walletState != WalletState.Loading) {
reportFullyDrawn()
}
2021-12-06 12:31:39 -08:00
}
}
}
}
2021-12-09 12:21:30 -08:00
@Composable
private fun WrapOnboarding() {
val onboardingViewModel by viewModels<OnboardingViewModel>()
if (!onboardingViewModel.isImporting.collectAsState().value) {
Onboarding(
onboardingState = onboardingViewModel.onboardingState,
onImportWallet = { onboardingViewModel.isImporting.value = true },
onCreateWallet = {
walletViewModel.persistNewWallet()
}
)
} else {
WrapRestore()
}
}
@Composable
private fun WrapBackup(persistableWallet: PersistableWallet) {
2021-12-09 12:21:30 -08:00
val backupViewModel by viewModels<BackupViewModel>()
2021-12-02 12:33:55 -08:00
BackupWallet(
persistableWallet, backupViewModel.backupState, backupViewModel.testChoices,
onCopyToClipboard = {
val clipboardManager = getSystemService(ClipboardManager::class.java)
val data = ClipData.newPlainText(
getString(R.string.new_wallet_clipboard_tag),
2021-12-09 12:21:30 -08:00
persistableWallet.seedPhrase.joinToString()
2021-12-02 12:33:55 -08:00
)
clipboardManager.setPrimaryClip(data)
}, onComplete = {
walletViewModel.persistBackupComplete()
}
2021-12-02 12:33:55 -08:00
)
}
@Composable
2021-12-09 12:21:30 -08:00
private fun WrapRestore() {
val onboardingViewModel by viewModels<OnboardingViewModel>()
val restoreViewModel by viewModels<RestoreViewModel>()
when (val completeWordList = restoreViewModel.completeWordList.collectAsState().value) {
CompleteWordSetState.Loading -> {
// Although it might perform IO, it should be relatively fast.
// Consider whether to display indeterminate progress here.
// Another option would be to go straight to the restore screen with autocomplete
// disabled for a few milliseconds. Users would probably never notice due to the
// time it takes to re-orient on the new screen, unless users were doing this
// on a daily basis and become very proficient at our UI. The Therac-25 has
// historical precedent on how that could cause problems.
}
2021-12-09 12:21:30 -08:00
is CompleteWordSetState.Loaded -> {
RestoreWallet(
completeWordList.list,
restoreViewModel.userWordList,
onBack = { onboardingViewModel.isImporting.value = false },
paste = {
val clipboardManager = getSystemService(ClipboardManager::class.java)
return@RestoreWallet clipboardManager?.primaryClip?.toString()
},
onFinished = {
// Write the backup complete flag first, then the seed phrase. That avoids the UI
// flickering to the backup screen. Assume if a user is restoring from
// a backup, then the user has a valid backup.
walletViewModel.persistBackupComplete()
val restoredWallet = PersistableWallet(
ZcashNetwork.fromResources(application),
null,
SeedPhrase(restoreViewModel.userWordList.current.value)
)
walletViewModel.persistExistingWallet(restoredWallet)
}
)
}
}
}
2021-12-03 05:19:15 -08:00
@Composable
private fun WrapHome(persistableWallet: PersistableWallet) {
Home(persistableWallet)
}
companion object {
@VisibleForTesting
internal val SPLASH_SCREEN_DELAY = 0.seconds
}
}
2021-12-06 12:31:39 -08:00
/**
* Pre-fetches fonts on Android N (API 25) and below.
*/
/*
* ResourcesCompat is used implicitly by Compose on older Android versions.
* The backwards compatibility library performs disk IO and then
* caches the results. This moves that IO off the main thread, to prevent ANRs and
* jank during app startup.
*/
private suspend fun prefetchFontLegacy(context: Context, @androidx.annotation.FontRes fontRes: Int) =
withContext(Dispatchers.IO) {
ResourcesCompat.getFont(context, fontRes)
}