Skip to content

Commit

Permalink
AND-4730 Improved the biometrics prompt display on the app launch (#1569
Browse files Browse the repository at this point in the history
)

* AND-4730 [Main Activity] Improved the app initialization process

* AND-4730 [Welcome / Home] Improved handling of intents when the app is launched
  • Loading branch information
iiiburnyiii authored and Mama1emon committed Oct 6, 2023
1 parent 92bf600 commit 4ae6962
Show file tree
Hide file tree
Showing 22 changed files with 284 additions and 256 deletions.
12 changes: 0 additions & 12 deletions app/src/main/java/com/tangem/tap/GlobalSettingsState.kt

This file was deleted.

194 changes: 98 additions & 96 deletions app/src/main/java/com/tangem/tap/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package com.tangem.tap

import android.app.Application
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
import androidx.core.os.bundleOf
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import arrow.core.getOrElse
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.snackbar.Snackbar
import com.tangem.core.navigation.AppScreen
Expand All @@ -32,7 +34,6 @@ import com.tangem.tap.common.DialogManager
import com.tangem.tap.common.OnActivityResultCallback
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.NotificationsHandler
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.shop.googlepay.GooglePayService
Expand All @@ -49,19 +50,15 @@ import com.tangem.tap.features.intentHandler.handlers.SellCurrencyIntentHandler
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.shop.redux.ShopAction
import com.tangem.tap.features.welcome.redux.WelcomeAction
import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphAction
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.lang.ref.WeakReference
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
Expand Down Expand Up @@ -117,8 +114,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
lateinit var walletConnectInteractor: WalletConnectInteractor

private val viewModel: MainViewModel by viewModels()
private var isInitializing: Boolean = true
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode?>

// TODO: fixme: inject through DI
private val intentProcessor: IntentProcessor = IntentProcessor()
Expand All @@ -130,25 +126,24 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()

override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()

if (!isDarkThemeFeatureEnabled(application)) {
setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
installSplashScreen()
installAppTheme() // We need to call it before onCreate to prevent unnecessary activity recreation

super.onCreate(savedInstanceState)

cardSdkLifecycleObserver.onCreate(context = this)

bootstrapMainStateUpdates(application)

splashScreen.setKeepOnScreenCondition { isInitializing }
installActivityDependencies()
observeAppThemeModeUpdates()

setContentView(R.layout.activity_main)
systemActions()
initContent()

checkGooglePayAvailability()
}

private fun installActivityDependencies() {
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))

cardSdkLifecycleObserver.onCreate(context = this)
tangemSdkManager = injectedTangemSdkManager
appStateHolder.tangemSdkManager = tangemSdkManager
backupService = BackupService.init(cardSdkConfigRepository.sdk, this)
Expand All @@ -157,11 +152,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
initUserWalletsListManager()
initIntentHandlers()

store.dispatch(
ShopAction.CheckIfGooglePayAvailable(
GooglePayService(createPaymentsClient(this), this),
),
)
store.dispatch(
DaggerGraphAction.SetActivityDependencies(
testerRouter = testerRouter,
Expand All @@ -174,9 +164,57 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
)
}

private fun isDarkThemeFeatureEnabled(application: Application): Boolean {
val featureToggle = (application as TapApplication).darkThemeFeatureToggle
return featureToggle.isDarkThemeEnabled
private fun installAppTheme() {
appThemeModeFlow = createAppThemeModeFlow()
val mode = runBlocking { appThemeModeFlow.filterNotNull().first() }

updateAppTheme(mode)
}

private fun observeAppThemeModeUpdates() {
appThemeModeFlow
.filterNotNull()
.flowWithLifecycle(lifecycle)
.onEach(::updateAppTheme)
.launchIn(lifecycleScope)
}

@SuppressLint("SourceLockedOrientationActivity")
private fun initContent() {
WindowCompat.setDecorFitsSystemWindows(window, false)

supportFragmentManager.registerFragmentLifecycleCallbacks(
NavBarInsetsFragmentLifecycleCallback(),
true,
)

requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}

private fun checkGooglePayAvailability() {
store.dispatch(
ShopAction.CheckIfGooglePayAvailable(
GooglePayService(createPaymentsClient(this), this),
),
)
}

private fun createAppThemeModeFlow(): SharedFlow<AppThemeMode?> {
val tapApplication = application as TapApplication
val featureToggle = tapApplication.darkThemeFeatureToggle

return if (featureToggle.isDarkThemeEnabled) {
tapApplication.getAppThemeModeUseCase()
.map { maybeMode ->
maybeMode.getOrElse { AppThemeMode.DEFAULT }
}
.shareIn(
scope = lifecycleScope + Dispatchers.IO,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5_000),
)
} else {
MutableStateFlow(AppThemeMode.FORCE_LIGHT)
}
}

override fun onStart() {
Expand All @@ -189,7 +227,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
// TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop
notificationsHandler = NotificationsHandler(binding.fragmentContainer)

navigateToInitialScreenIfNeededOnResume(intent)
navigateToInitialScreenIfNeeded(intent)
}

override fun onStop() {
Expand All @@ -207,12 +245,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac

private fun initIntentHandlers() {
val hasSavedWalletsProvider = { store.state.globalState.userWalletsListManager?.hasUserWallets == true }
intentProcessor.addHandler(
BackgroundScanIntentHandler(
hasSavedWalletsProvider,
lifecycleScope,
),
)
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
intentProcessor.addHandler(BuyCurrencyIntentHandler())
intentProcessor.addHandler(SellCurrencyIntentHandler())
Expand All @@ -230,56 +263,28 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
store.dispatch(GlobalAction.UpdateUserWalletsListManager(manager))
}

private fun bootstrapMainStateUpdates(application: Application) {
viewModel.state
.onEach { state ->
isInitializing = state is GlobalSettingsState.Loading

when (state) {
is GlobalSettingsState.Content -> {
if (isDarkThemeFeatureEnabled(application)) {
MutableAppThemeModeHolder.value = state.appThemeMode
MutableAppThemeModeHolder.isDarkThemeActive = isDarkTheme()

val mode = when (state.appThemeMode) {
AppThemeMode.FORCE_DARK -> AppCompatDelegate.MODE_NIGHT_YES
AppThemeMode.FORCE_LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
AppThemeMode.FOLLOW_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
setDefaultNightMode(mode)
} else {
MutableAppThemeModeHolder.value = AppThemeMode.FORCE_LIGHT
}
}
is GlobalSettingsState.Loading -> Unit
}
}
.launchIn(lifecycleScope)
private fun updateAppTheme(appThemeMode: AppThemeMode) {
MutableAppThemeModeHolder.value = appThemeMode
MutableAppThemeModeHolder.isDarkThemeActive = isDarkTheme()

val mode = when (appThemeMode) {
AppThemeMode.FORCE_DARK -> AppCompatDelegate.MODE_NIGHT_YES
AppThemeMode.FORCE_LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
AppThemeMode.FOLLOW_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}

setDefaultNightMode(mode)
}

private fun isDarkTheme(): Boolean {
return when (
resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK
) {
return when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> true
Configuration.UI_MODE_NIGHT_NO -> false
Configuration.UI_MODE_NIGHT_UNDEFINED -> false
else -> false
}
}

private fun systemActions() {
WindowCompat.setDecorFitsSystemWindows(window, false)

supportFragmentManager.registerFragmentLifecycleCallbacks(
NavBarInsetsFragmentLifecycleCallback(),
true,
)

requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)

Expand Down Expand Up @@ -341,43 +346,40 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
lockUserWalletsTimer?.restart()
}

private fun navigateToInitialScreenIfNeededOnResume(intentWhichStartedActivity: Intent?) {
private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) {
val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
val isNotScannedBefore = store.state.globalState.scanResponse == null
val isOnboardingServiceNotActive = store.state.globalState.onboardingState.onboardingStarted
val isShopNotOpened = store.state.shopState.total != null

when {
!backStackIsEmpty && isNotScannedBefore && isOnboardingServiceNotActive && isShopNotOpened -> {
navigateToInitialScreenOnResume(intentWhichStartedActivity)
navigateToInitialScreen(intentWhichStartedActivity)
}
backStackIsEmpty -> {
navigateToInitialScreenOnResume(intentWhichStartedActivity)
navigateToInitialScreen(intentWhichStartedActivity)
}
else -> Unit
}
}

private fun navigateToInitialScreenOnResume(intentWhichStartedActivity: Intent?) {
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
if (store.state.globalState.userWalletsListManager?.hasUserWallets == true) {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome))
store.dispatchOnMain(WelcomeAction.SetInitialIntent(intentWhichStartedActivity))
lifecycleScope.launch {
val handler = BackgroundScanIntentHandler(
hasSavedUserWalletsProvider = { true },
lifecycleCoroutineScope = lifecycleScope,
)
val isBackgroundScanHandled = handler.handleIntent(intentWhichStartedActivity)
val hasNotIncompletedBackup = !backupService.hasIncompletedBackup
if (!isBackgroundScanHandled && hasNotIncompletedBackup) {
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics)
}
}
store.dispatch(
NavigationAction.NavigateTo(
screen = AppScreen.Welcome,
bundle = intentWhichStartedActivity?.let {
bundleOf(WelcomeFragment.INITIAL_INTENT_KEY to it)
},
),
)
} else {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home))
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
lifecycleScope.launch {
intentProcessor.handleIntent(intentWhichStartedActivity)
}
}

store.dispatch(BackupAction.CheckForUnfinishedBackup)
}
}
35 changes: 0 additions & 35 deletions app/src/main/java/com/tangem/tap/MainViewModel.kt

This file was deleted.

4 changes: 4 additions & 0 deletions app/src/main/java/com/tangem/tap/TapApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.tangem.datasource.config.models.Config
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.card.ScanCardProcessor
Expand Down Expand Up @@ -200,6 +201,9 @@ internal class TapApplication : Application(), ImageLoaderFactory {

@Inject
lateinit var appRatingRepository: AppRatingRepository

@Inject
lateinit var getAppThemeModeUseCase: GetAppThemeModeUseCase
// endregion Injected

override fun onCreate() {
Expand Down
Loading

0 comments on commit 4ae6962

Please sign in to comment.