diff --git a/ui-lib/src/main/java/cash/z/ecc/ui/MainActivity.kt b/ui-lib/src/main/java/cash/z/ecc/ui/MainActivity.kt index a084b780..6fd1d18e 100644 --- a/ui-lib/src/main/java/cash/z/ecc/ui/MainActivity.kt +++ b/ui-lib/src/main/java/cash/z/ecc/ui/MainActivity.kt @@ -2,6 +2,7 @@ package cash.z.ecc.ui import android.content.ClipData import android.content.ClipboardManager +import android.content.Context import android.os.Bundle import android.os.SystemClock import androidx.activity.ComponentActivity @@ -10,7 +11,9 @@ import androidx.activity.viewModels import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.core.content.res.ResourcesCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.lifecycle.lifecycleScope import cash.z.ecc.sdk.model.PersistableWallet import cash.z.ecc.ui.screen.backup.view.BackupWallet import cash.z.ecc.ui.screen.backup.viewmodel.BackupViewModel @@ -20,6 +23,10 @@ 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 import cash.z.ecc.ui.theme.ZcashTheme +import cash.z.ecc.ui.util.AndroidApiVersion +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -36,6 +43,37 @@ class MainActivity : ComponentActivity() { setupSplashScreen() + if (AndroidApiVersion.isAtLeastO) { + setupUiContent() + } else { + lifecycleScope.launch { + prefetchFontLegacy(applicationContext, R.font.rubik_medium) + prefetchFontLegacy(applicationContext, R.font.rubik_regular) + + setupUiContent() + } + } + } + + private fun setupSplashScreen() { + installSplashScreen().also { + val start = SystemClock.elapsedRealtime().milliseconds + it.setKeepVisibleCondition { + if (SPLASH_SCREEN_DELAY > Duration.ZERO) { + val now = SystemClock.elapsedRealtime().milliseconds + + // This delay is for debug purposes only; do not enable for production usage. + if (now - start < SPLASH_SCREEN_DELAY) { + return@setKeepVisibleCondition true + } + } + + WalletState.Loading == walletViewModel.state.value + } + } + } + + private fun setupUiContent() { setContent { ZcashTheme { val walletState = walletViewModel.state.collectAsState().value @@ -59,24 +97,6 @@ class MainActivity : ComponentActivity() { } } - private fun setupSplashScreen() { - installSplashScreen().also { - val start = SystemClock.elapsedRealtime().milliseconds - it.setKeepVisibleCondition { - if (SPLASH_SCREEN_DELAY > Duration.ZERO) { - val now = SystemClock.elapsedRealtime().milliseconds - - // This delay is for debug purposes only; do not enable for production usage. - if (now - start < SPLASH_SCREEN_DELAY) { - return@setKeepVisibleCondition true - } - } - - WalletState.Loading == walletViewModel.state.value - } - } - } - @Composable private fun WrapBackup(persistableWallet: PersistableWallet) { BackupWallet( @@ -115,3 +135,17 @@ class MainActivity : ComponentActivity() { internal val SPLASH_SCREEN_DELAY = 0.seconds } } + +/** + * 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) + } diff --git a/ui-lib/src/main/java/cash/z/ecc/ui/util/AndroidApiVersion.kt b/ui-lib/src/main/java/cash/z/ecc/ui/util/AndroidApiVersion.kt new file mode 100644 index 00000000..15425d45 --- /dev/null +++ b/ui-lib/src/main/java/cash/z/ecc/ui/util/AndroidApiVersion.kt @@ -0,0 +1,34 @@ +package cash.z.ecc.ui.util + +import android.os.Build +import androidx.annotation.ChecksSdkIntAtLeast +import androidx.annotation.IntRange + +object AndroidApiVersion { + /** + * @param sdk SDK version number to test against the current environment. + * @return `true` if [android.os.Build.VERSION.SDK_INT] is greater than or equal to + * [sdk]. + */ + @ChecksSdkIntAtLeast(parameter = 0) + fun isAtLeast(@IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int): Boolean { + return Build.VERSION.SDK_INT >= sdk + } + + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P) + val isAtLeastP = isAtLeast(Build.VERSION_CODES.P) + + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O) + val isAtLeastO = isAtLeast(Build.VERSION_CODES.O) + + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q) + val isAtLeastQ = isAtLeast(Build.VERSION_CODES.Q) + + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R) + val isAtLeastR = isAtLeast(Build.VERSION_CODES.R) + + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) + val isAtLeastS = isAtLeast(Build.VERSION_CODES.S) + + val isPreview = 0 != Build.VERSION.PREVIEW_SDK_INT +}