716 lines
No EOL
27 KiB
Kotlin
716 lines
No EOL
27 KiB
Kotlin
package com.tangem.tap
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.content.Intent
|
|
import android.content.pm.ActivityInfo
|
|
import android.content.res.Configuration
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import android.view.MotionEvent
|
|
import android.view.WindowManager
|
|
import androidx.activity.SystemBarStyle
|
|
import androidx.activity.compose.setContent
|
|
import androidx.activity.enableEdgeToEdge
|
|
import androidx.activity.viewModels
|
|
import androidx.annotation.StringRes
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.appcompat.app.AppCompatDelegate
|
|
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.toArgb
|
|
import androidx.compose.ui.platform.ViewCompositionStrategy
|
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE
|
|
import androidx.lifecycle.Lifecycle
|
|
import androidx.lifecycle.flowWithLifecycle
|
|
import androidx.lifecycle.lifecycleScope
|
|
import arrow.core.getOrElse
|
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
|
import com.arkivanov.decompose.value.subscribe
|
|
import com.arkivanov.essenty.lifecycle.asEssentyLifecycle
|
|
import com.google.android.material.snackbar.BaseTransientBottomBar
|
|
import com.google.android.material.snackbar.Snackbar
|
|
import com.tangem.common.routing.AppRoute
|
|
import com.tangem.common.routing.entity.SerializableIntent
|
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|
import com.tangem.core.decompose.context.AppComponentContext
|
|
import com.tangem.core.decompose.di.RootAppComponentContext
|
|
import com.tangem.core.deeplink.DeepLinksRegistry
|
|
import com.tangem.core.navigation.email.EmailSender
|
|
import com.tangem.core.ui.UiDependencies
|
|
import com.tangem.core.ui.extensions.TextReference
|
|
import com.tangem.core.ui.extensions.resolveReference
|
|
import com.tangem.core.ui.message.EventMessageEffect
|
|
import com.tangem.core.ui.message.SnackbarMessage
|
|
import com.tangem.core.ui.res.TangemColorPalette
|
|
import com.tangem.core.ui.res.TangemTheme
|
|
import com.tangem.data.balancehiding.DefaultDeviceFlipDetector
|
|
import com.tangem.data.card.sdk.CardSdkOwner
|
|
import com.tangem.domain.apptheme.model.AppThemeMode
|
|
import com.tangem.domain.card.ScanCardUseCase
|
|
import com.tangem.domain.card.repository.CardRepository
|
|
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
|
import com.tangem.domain.settings.SetGooglePayAvailabilityUseCase
|
|
import com.tangem.domain.settings.SetGoogleServicesAvailabilityUseCase
|
|
import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase
|
|
import com.tangem.domain.settings.repositories.SettingsRepository
|
|
import com.tangem.domain.staking.SendUnsubmittedHashesUseCase
|
|
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
|
|
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
|
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
|
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
|
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
|
import com.tangem.google.GoogleServicesHelper
|
|
import com.tangem.operations.backup.BackupService
|
|
import com.tangem.sdk.api.BackupServiceHolder
|
|
import com.tangem.sdk.api.TangemSdkManager
|
|
import com.tangem.sdk.extensions.init
|
|
import com.tangem.tap.common.ActivityResultCallbackHolder
|
|
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.dispatchNavigationAction
|
|
import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss
|
|
import com.tangem.tap.common.redux.NotificationsHandler
|
|
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
|
import com.tangem.tap.features.intentHandler.IntentProcessor
|
|
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
|
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
|
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
|
import com.tangem.tap.features.main.MainViewModel
|
|
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
|
import com.tangem.tap.proxy.AppStateHolder
|
|
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
|
import com.tangem.tap.routing.component.RoutingComponent
|
|
import com.tangem.tap.routing.configurator.AppRouterConfig
|
|
import com.tangem.tap.routing.toggle.RoutingFeatureToggles
|
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|
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.*
|
|
import kotlinx.coroutines.flow.*
|
|
import timber.log.Timber
|
|
import javax.inject.Inject
|
|
import kotlin.coroutines.CoroutineContext
|
|
import kotlin.time.Duration.Companion.seconds
|
|
|
|
lateinit var tangemSdkManager: TangemSdkManager
|
|
lateinit var backupService: BackupService
|
|
internal var lockUserWalletsTimer: LockUserWalletsTimer? = null
|
|
private set
|
|
var notificationsHandler: NotificationsHandler? = null
|
|
|
|
private val coroutineContext: CoroutineContext
|
|
get() = SupervisorJob() + Dispatchers.IO + FeatureCoroutineExceptionHandler.create("scope")
|
|
val scope = CoroutineScope(coroutineContext)
|
|
|
|
private val mainCoroutineContext: CoroutineContext
|
|
get() = SupervisorJob() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope")
|
|
val mainScope = CoroutineScope(mainCoroutineContext)
|
|
|
|
@Suppress("LargeClass")
|
|
@AndroidEntryPoint
|
|
class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder {
|
|
|
|
@Inject
|
|
lateinit var appStateHolder: AppStateHolder
|
|
|
|
/** Router for opening tester menu */
|
|
@Inject
|
|
lateinit var cardSdkOwner: CardSdkOwner
|
|
|
|
@Inject
|
|
lateinit var cardSdkConfigRepository: CardSdkConfigRepository
|
|
|
|
@Inject
|
|
lateinit var injectedTangemSdkManager: TangemSdkManager
|
|
|
|
@Inject
|
|
lateinit var scanCardUseCase: ScanCardUseCase
|
|
|
|
@Inject
|
|
lateinit var walletConnectInteractor: WalletConnectInteractor
|
|
|
|
@Inject
|
|
lateinit var deepLinksRegistry: DeepLinksRegistry
|
|
|
|
@Inject
|
|
lateinit var settingsRepository: SettingsRepository
|
|
|
|
@Inject
|
|
lateinit var sendUnsubmittedHashesUseCase: SendUnsubmittedHashesUseCase
|
|
|
|
@Inject
|
|
lateinit var getPolkadotCheckHasResetUseCase: GetPolkadotCheckHasResetUseCase
|
|
|
|
@Inject
|
|
lateinit var getPolkadotCheckHasImmortalUseCase: GetPolkadotCheckHasImmortalUseCase
|
|
|
|
@Inject
|
|
lateinit var analyticsEventsHandler: AnalyticsEventHandler
|
|
|
|
@Inject
|
|
lateinit var userWalletsListManager: UserWalletsListManager
|
|
|
|
@Inject
|
|
lateinit var emailSender: EmailSender
|
|
|
|
@Inject
|
|
@RootAppComponentContext
|
|
internal lateinit var rootComponentContext: AppComponentContext
|
|
|
|
@Inject
|
|
internal lateinit var appRouterConfig: AppRouterConfig
|
|
|
|
@Inject
|
|
internal lateinit var routingComponentFactory: RoutingComponent.Factory
|
|
|
|
@Inject
|
|
lateinit var cardRepository: CardRepository
|
|
|
|
@Inject
|
|
lateinit var shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase
|
|
|
|
@Inject
|
|
lateinit var backupServiceHolder: BackupServiceHolder
|
|
|
|
@Inject
|
|
lateinit var onboardingV2FeatureToggles: OnboardingV2FeatureToggles
|
|
|
|
@Inject
|
|
lateinit var setGoogleServicesAvailabilityUseCase: SetGoogleServicesAvailabilityUseCase
|
|
|
|
@Inject
|
|
lateinit var setGooglePayAvailabilityUseCase: SetGooglePayAvailabilityUseCase
|
|
|
|
@Inject
|
|
lateinit var dispatchers: CoroutineDispatcherProvider
|
|
|
|
@Inject
|
|
internal lateinit var routingFeatureToggles: RoutingFeatureToggles
|
|
|
|
@Inject
|
|
internal lateinit var uiDependencies: UiDependencies
|
|
|
|
@Inject
|
|
internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector
|
|
|
|
internal val viewModel: MainViewModel by viewModels()
|
|
|
|
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
|
|
|
// TODO: fixme: inject through DI
|
|
private val intentProcessor: IntentProcessor = IntentProcessor()
|
|
|
|
private var snackbar: Snackbar? = null
|
|
private val dialogManager = DialogManager()
|
|
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
|
|
|
|
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
// We need to call it before onCreate to prevent unnecessary activity recreation
|
|
installAppTheme()
|
|
|
|
val splashScreen = installSplashScreen()
|
|
|
|
enableEdgeToEdge(
|
|
navigationBarStyle = SystemBarStyle.auto(
|
|
Color.Transparent.toArgb(),
|
|
Color.Transparent.toArgb(),
|
|
),
|
|
)
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
// We have to allow adjust_resize (can only be specified in the manifest) for Android <=10,
|
|
// in order for imePadding to work correctly
|
|
// for Android 11+ we set SOFT_INPUT_ADJUST_NOTHING to prevent resizing the layout
|
|
// so that we don't have any distortions in the layout when displaying the keyboard
|
|
// https://issuetracker.google.com/issues/266331465
|
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
|
|
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
window.setHideOverlayWindows(true)
|
|
}
|
|
|
|
splashScreen.setKeepOnScreenCondition { viewModel.isSplashScreenShown }
|
|
|
|
installActivityDependencies()
|
|
observeAppThemeModeUpdates()
|
|
|
|
if (routingFeatureToggles.isNavigationRefactoringEnabled) {
|
|
setRootContent()
|
|
} else {
|
|
setContentView(R.layout.activity_main)
|
|
installRouting()
|
|
installEventMessageEffect()
|
|
}
|
|
|
|
initContent()
|
|
|
|
observePolkadotAccountHealthCheck()
|
|
sendStakingUnsubmittedHashes()
|
|
checkGoogleServicesAvailability()
|
|
|
|
if (intent != null && savedInstanceState == null) {
|
|
// handle intent only on start, not on recreate
|
|
deepLinksRegistry.launch(intent)
|
|
}
|
|
|
|
lifecycle.addObserver(WindowObscurationObserver)
|
|
lifecycle.addObserver(defaultDeviceFlipDetector)
|
|
}
|
|
|
|
private fun installEventMessageEffect() {
|
|
binding.composeView.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
|
binding.composeView.setContent {
|
|
TangemTheme(
|
|
activity = this,
|
|
uiDependencies = uiDependencies,
|
|
overrideSystemBarColors = false,
|
|
) {
|
|
EventMessageEffect(
|
|
onShowSnackbar = { message, _ ->
|
|
showSnackbar(
|
|
text = message.message.resolveReference(resources),
|
|
length = when (message.duration) {
|
|
SnackbarMessage.Duration.Short -> Snackbar.LENGTH_SHORT
|
|
SnackbarMessage.Duration.Long -> Snackbar.LENGTH_LONG
|
|
SnackbarMessage.Duration.Indefinite -> Snackbar.LENGTH_INDEFINITE
|
|
},
|
|
buttonTitle = message.actionLabel?.resolveReference(resources),
|
|
action = message.action,
|
|
onDismiss = message.onDismissRequest,
|
|
)
|
|
},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun setRootContent() {
|
|
// for now activity is singleTop and after going to ChromeCustomTab it calls onCreate but onDestroy
|
|
// doesn't calls. It lead to issue that decompose nav stack is not saved in bundle and to restore it
|
|
// we try to init component with previous stack
|
|
val routingComponent = routingComponentFactory.create(
|
|
context = rootComponentContext,
|
|
initialStack = appRouterConfig.stack,
|
|
)
|
|
|
|
setContent {
|
|
routingComponent.Content(Modifier.fillMaxSize())
|
|
}
|
|
}
|
|
|
|
private fun installRouting() {
|
|
// for now activity is singleTop and after going to ChromeCustomTab it calls onCreate but onDestroy
|
|
// doesn't calls. It lead to issue that decompose nav stack is not saved in bundle and to restore it
|
|
// we try to init component with previous stack
|
|
val routingComponent = routingComponentFactory.create(
|
|
context = rootComponentContext,
|
|
initialStack = appRouterConfig.stack,
|
|
)
|
|
|
|
appRouterConfig.routerScope = mainScope
|
|
appRouterConfig.componentRouter = routingComponent.router
|
|
appRouterConfig.snackbarHandler = this
|
|
|
|
routingComponent.stack.subscribe(lifecycle.asEssentyLifecycle()) { childStack ->
|
|
val stack = childStack.backStack
|
|
.plus(childStack.active)
|
|
.map { it.configuration }
|
|
|
|
if (stack == appRouterConfig.stack) return@subscribe
|
|
|
|
appRouterConfig.stack = stack
|
|
|
|
if (stack.size == 1) {
|
|
supportFragmentManager.run {
|
|
val activeChildName = stack.first().path
|
|
|
|
(0 until backStackEntryCount)
|
|
.mapNotNull { getBackStackEntryAt(it).name }
|
|
.filter { it != activeChildName }
|
|
.forEach {
|
|
popBackStackImmediate(it, POP_BACK_STACK_INCLUSIVE)
|
|
}
|
|
}
|
|
}
|
|
|
|
when (val child = childStack.active.instance) {
|
|
is RoutingComponent.Child.Initial -> Unit
|
|
is RoutingComponent.Child.LegacyFragment -> {
|
|
supportFragmentManager.showFragmentAllowingStateLoss(child.name, child.fragmentProvider)
|
|
}
|
|
is RoutingComponent.Child.LegacyIntent -> {
|
|
startActivity(child.intent)
|
|
}
|
|
is RoutingComponent.Child.ComposableComponent -> error("Unsupported child: $child")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun installActivityDependencies() {
|
|
cardSdkOwner.register(activity = this)
|
|
tangemSdkManager = injectedTangemSdkManager
|
|
|
|
if (onboardingV2FeatureToggles.isOnboardingV2Enabled) {
|
|
backupServiceHolder.createAndSetService(cardSdkConfigRepository.sdk, this)
|
|
backupService = backupServiceHolder.backupService.get()!! // will be deleted eventually
|
|
} else {
|
|
backupService = BackupService.init(cardSdkConfigRepository.sdk, this)
|
|
}
|
|
|
|
lockUserWalletsTimer = LockUserWalletsTimer(
|
|
context = this,
|
|
settingsRepository = settingsRepository,
|
|
userWalletsListManager = userWalletsListManager,
|
|
coroutineScope = mainScope,
|
|
)
|
|
|
|
initIntentHandlers()
|
|
|
|
store.dispatch(
|
|
DaggerGraphAction.SetActivityDependencies(
|
|
scanCardUseCase = scanCardUseCase,
|
|
walletConnectInteractor = walletConnectInteractor,
|
|
cardSdkConfigRepository = cardSdkConfigRepository,
|
|
),
|
|
)
|
|
}
|
|
|
|
private fun installAppTheme() {
|
|
appThemeModeFlow = createAppThemeModeFlow()
|
|
val mode = runBlocking {
|
|
withTimeoutOrNull(APP_THEME_LOAD_TIMEOUT.seconds) {
|
|
appThemeModeFlow.first()
|
|
} ?: AppThemeMode.DEFAULT
|
|
}
|
|
|
|
updateAppTheme(mode)
|
|
}
|
|
|
|
private fun observeAppThemeModeUpdates() {
|
|
appThemeModeFlow
|
|
.flowWithLifecycle(lifecycle)
|
|
.onEach(::updateAppTheme)
|
|
.launchIn(lifecycleScope)
|
|
}
|
|
|
|
@SuppressLint("SourceLockedOrientationActivity")
|
|
private fun initContent() {
|
|
supportFragmentManager.registerFragmentLifecycleCallbacks(
|
|
NavBarInsetsFragmentLifecycleCallback(),
|
|
true,
|
|
)
|
|
|
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
}
|
|
|
|
private fun createAppThemeModeFlow(): SharedFlow<AppThemeMode> {
|
|
val tangemApplication = application as TangemApplication
|
|
|
|
return tangemApplication.getAppThemeModeUseCase()
|
|
.filterNotNull()
|
|
.distinctUntilChanged()
|
|
.map { maybeMode ->
|
|
maybeMode.getOrElse { AppThemeMode.DEFAULT }
|
|
}
|
|
.shareIn(
|
|
scope = lifecycleScope,
|
|
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5_000),
|
|
)
|
|
}
|
|
|
|
override fun onStart() {
|
|
super.onStart()
|
|
dialogManager.onStart(this)
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
|
|
if (!routingFeatureToggles.isNavigationRefactoringEnabled) {
|
|
// TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop
|
|
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
|
|
}
|
|
|
|
navigateToInitialScreenIfNeeded(intent)
|
|
}
|
|
|
|
override fun onStop() {
|
|
notificationsHandler = null
|
|
dialogManager.onStop()
|
|
super.onStop()
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
intentProcessor.removeAll()
|
|
// workaround: kill process when activity destroy to avoid state when lock() wallets
|
|
// and navigation to unlock screen was skipped because system kills activity but not process
|
|
android.os.Process.killProcess(android.os.Process.myPid())
|
|
super.onDestroy()
|
|
}
|
|
|
|
private fun initIntentHandlers() {
|
|
val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets }
|
|
intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler))
|
|
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
|
|
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
|
|
}
|
|
|
|
private fun updateAppTheme(appThemeMode: AppThemeMode) {
|
|
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)
|
|
|
|
MutableAppThemeModeHolder.value = appThemeMode
|
|
MutableAppThemeModeHolder.isDarkThemeActive = isDarkTheme()
|
|
}
|
|
|
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
super.onConfigurationChanged(newConfig)
|
|
|
|
/*
|
|
* We need to manually change the background color of the activity when the UI mode changes to prevent
|
|
* flickering when navigating between fragments.
|
|
*
|
|
|
|
* `android:configChanges="uiMode"` is set in the manifest.
|
|
* */
|
|
if (routingFeatureToggles.isNavigationRefactoringEnabled.not()) {
|
|
updateAppBackground()
|
|
}
|
|
}
|
|
|
|
private fun updateAppBackground() {
|
|
val backgroundColor = if (isDarkTheme()) {
|
|
TangemColorPalette.Dark6
|
|
} else {
|
|
TangemColorPalette.White
|
|
}
|
|
|
|
findViewById<CoordinatorLayout>(R.id.fragment_container).setBackgroundColor(backgroundColor.toArgb())
|
|
}
|
|
|
|
private fun isDarkTheme(): Boolean {
|
|
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
|
|
}
|
|
}
|
|
|
|
override fun onNewIntent(intent: Intent?) {
|
|
super.onNewIntent(intent)
|
|
|
|
lifecycleScope.launch {
|
|
intentProcessor.handleIntent(intent = intent, isFromForeground = true)
|
|
}
|
|
|
|
if (intent != null) {
|
|
deepLinksRegistry.launch(intent)
|
|
}
|
|
}
|
|
|
|
override fun showSnackbar(@StringRes text: Int, length: Int, @StringRes buttonTitle: Int?, action: (() -> Unit)?) {
|
|
showSnackbar(
|
|
getString(text),
|
|
length,
|
|
buttonTitle?.let(::getString),
|
|
action = { action?.invoke() },
|
|
)
|
|
}
|
|
|
|
override fun showSnackbar(text: TextReference, length: Int, buttonTitle: TextReference?, action: (() -> Unit)?) {
|
|
showSnackbar(
|
|
text = text.resolveReference(resources),
|
|
length = length,
|
|
buttonTitle = buttonTitle?.resolveReference(resources),
|
|
action = { action?.invoke() },
|
|
)
|
|
}
|
|
|
|
override fun dismissSnackbar() {
|
|
snackbar?.dismiss()
|
|
snackbar = null
|
|
}
|
|
|
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
super.onActivityResult(requestCode, resultCode, data)
|
|
|
|
onActivityResultCallbacks.forEach { it(requestCode, resultCode, data) }
|
|
}
|
|
|
|
override fun addOnActivityResultCallback(callback: OnActivityResultCallback) {
|
|
onActivityResultCallbacks.remove(callback)
|
|
onActivityResultCallbacks.add(callback)
|
|
}
|
|
|
|
override fun removeOnActivityResultCallback(callback: OnActivityResultCallback) {
|
|
onActivityResultCallbacks.remove(callback)
|
|
}
|
|
|
|
override fun onUserInteraction() {
|
|
super.onUserInteraction()
|
|
|
|
lockUserWalletsTimer?.restart()
|
|
}
|
|
|
|
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
val result = WindowObscurationObserver.dispatchTouchEvent(event, analyticsEventsHandler)
|
|
|
|
return if (result) super.dispatchTouchEvent(event) else false
|
|
}
|
|
|
|
private fun showSnackbar(
|
|
text: String,
|
|
length: Int,
|
|
buttonTitle: String?,
|
|
action: (() -> Unit)? = null,
|
|
onDismiss: () -> Unit = {},
|
|
) {
|
|
if (snackbar != null) return
|
|
|
|
snackbar = Snackbar.make(binding.fragmentContainer, text, length).apply {
|
|
val textColor = getColor(R.color.text_primary_2)
|
|
|
|
setBackgroundTint(getColor(R.color.button_primary))
|
|
setActionTextColor(textColor)
|
|
setTextColor(textColor)
|
|
|
|
if (buttonTitle != null && action != null) {
|
|
setAction(buttonTitle, { action() })
|
|
}
|
|
|
|
addCallback(
|
|
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
|
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
|
|
onDismiss()
|
|
snackbar = null
|
|
removeCallback(this)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
snackbar?.show()
|
|
}
|
|
|
|
private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) {
|
|
val backStack = appRouterConfig.stack ?: emptyList()
|
|
// TODO move inital navigation to navigation component ([REDACTED_JIRA])
|
|
val isOnlyInitialRoute = backStack.all { it is AppRoute.Initial }
|
|
val isOnInitialScreen = backStack.all { it is AppRoute.Welcome || it is AppRoute.Home }
|
|
val isNotScannedBefore = store.state.globalState.scanResponse == null
|
|
val isOnboardingServiceNotActive = !store.state.globalState.onboardingState.onboardingStarted
|
|
|
|
when {
|
|
!isOnInitialScreen && isNotScannedBefore && isOnboardingServiceNotActive -> {
|
|
navigateToInitialScreen(intentWhichStartedActivity)
|
|
}
|
|
backStack.isEmpty() -> {
|
|
navigateToInitialScreen(intentWhichStartedActivity)
|
|
}
|
|
isOnlyInitialRoute -> navigateToInitialScreen(intentWhichStartedActivity)
|
|
else -> Unit
|
|
}
|
|
}
|
|
|
|
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
|
|
if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
|
|
store.dispatchNavigationAction {
|
|
replaceAll(AppRoute.Welcome(intentWhichStartedActivity?.let(::SerializableIntent)))
|
|
}
|
|
intentProcessor.handleIntent(
|
|
intent = intentWhichStartedActivity,
|
|
isFromForeground = false,
|
|
skipNavigationHandlers = true,
|
|
)
|
|
} else {
|
|
lifecycleScope.launch {
|
|
val shouldShowTos = !cardRepository.isTangemTOSAccepted()
|
|
val shouldShowInitialPush = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { false }
|
|
|
|
val route = when {
|
|
shouldShowTos -> AppRoute.Disclaimer(isTosAccepted = false)
|
|
shouldShowInitialPush -> AppRoute.PushNotification
|
|
else -> AppRoute.Home
|
|
}
|
|
|
|
store.dispatchNavigationAction { replaceAll(route) }
|
|
intentProcessor.handleIntent(
|
|
intent = intentWhichStartedActivity,
|
|
isFromForeground = false,
|
|
skipNavigationHandlers = false,
|
|
)
|
|
}
|
|
}
|
|
|
|
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
|
}
|
|
|
|
private fun observePolkadotAccountHealthCheck() {
|
|
lifecycleScope.launch {
|
|
getPolkadotCheckHasResetUseCase()
|
|
.flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED)
|
|
.distinctUntilChanged()
|
|
.collect {
|
|
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotAccountReset(it.second))
|
|
}
|
|
}
|
|
lifecycleScope.launch {
|
|
getPolkadotCheckHasImmortalUseCase()
|
|
.flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED)
|
|
.distinctUntilChanged()
|
|
.collect {
|
|
analyticsEventsHandler.send(
|
|
WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(it.second),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun sendStakingUnsubmittedHashes() {
|
|
lifecycleScope.launch {
|
|
sendUnsubmittedHashesUseCase.invoke()
|
|
.onLeft { Timber.e(it.toString()) }
|
|
.onRight { Timber.d("Submitting hashes succeeded") }
|
|
}
|
|
}
|
|
|
|
private fun checkGoogleServicesAvailability() {
|
|
val isGoogleServicesAvailable = GoogleServicesHelper.checkGoogleServicesAvailability(this)
|
|
|
|
lifecycleScope.launch {
|
|
setGoogleServicesAvailabilityUseCase(isGoogleServicesAvailable)
|
|
|
|
if (isGoogleServicesAvailable) {
|
|
val paymentsClient = GoogleServicesHelper.createPaymentsClient(this@MainActivity)
|
|
val isGooglePayAvailable = GoogleServicesHelper.checkGooglePayAvailability(paymentsClient)
|
|
setGooglePayAvailabilityUseCase(isGooglePayAvailable)
|
|
} else {
|
|
setGooglePayAvailabilityUseCase(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
private const val APP_THEME_LOAD_TIMEOUT = 2
|
|
}
|
|
} |