Updated on 2026-08-14

This commit is contained in:
Tangem 2024-07-10 17:17:40 +02:00
commit 3debe69f54
903 changed files with 22403 additions and 10801 deletions

View file

@ -14,6 +14,11 @@ android {
testOptions { testOptions {
animationsDisabled = true animationsDisabled = true
} }
packaging {
jniLibs {
useLegacyPackaging = true
}
}
} }
configurations.all { configurations.all {
@ -71,6 +76,7 @@ dependencies {
implementation(projects.domain.walletConnect) implementation(projects.domain.walletConnect)
implementation(projects.common) implementation(projects.common)
implementation(projects.common.routing)
implementation(projects.core.analytics) implementation(projects.core.analytics)
implementation(projects.core.analytics.models) implementation(projects.core.analytics.models)
implementation(projects.core.navigation) implementation(projects.core.navigation)
@ -130,6 +136,12 @@ dependencies {
implementation(projects.features.staking.impl) implementation(projects.features.staking.impl)
implementation(projects.features.details.api) implementation(projects.features.details.api)
implementation(projects.features.details.impl) implementation(projects.features.details.impl)
implementation(projects.features.disclaimer.api)
implementation(projects.features.disclaimer.impl)
implementation(projects.features.pushNotifications.api)
implementation(projects.features.pushNotifications.impl)
implementation(projects.features.walletSettings.api)
implementation(projects.features.walletSettings.impl)
/** AndroidX libraries */ /** AndroidX libraries */
implementation(deps.androidx.core.ktx) implementation(deps.androidx.core.ktx)
@ -166,6 +178,7 @@ dependencies {
implementation(platform(deps.firebase.bom)) implementation(platform(deps.firebase.bom))
implementation(deps.firebase.analytics) implementation(deps.firebase.analytics)
implementation(deps.firebase.crashlytics) implementation(deps.firebase.crashlytics)
implementation(deps.firebase.messaging)
/** Tangem libraries */ /** Tangem libraries */
implementation(deps.tangem.blockchain) { implementation(deps.tangem.blockchain) {

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="resizeable_activity">true</bool>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="resizeable_activity">true</bool>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="resizeable_activity">true</bool>
</resources>

View file

@ -40,11 +40,11 @@
android:fullBackupContent="false" android:fullBackupContent="false"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:resizeableActivity="@bool/resizeable_activity"
android:label="@string/tangem_app_name" android:label="@string/tangem_app_name"
android:largeHeap="@bool/largeHeap" android:largeHeap="@bool/largeHeap"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher"
android:extractNativeLibs="true"
android:supportsRtl="true" android:supportsRtl="true"
tools:ignore="GoogleAppIndexingWarning" tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:allowBackup, android:fullBackupContent, android:label"> tools:replace="android:allowBackup, android:fullBackupContent, android:label">
@ -146,5 +146,12 @@
android:resource="@xml/provider_paths" /> android:resource="@xml/provider_paths" />
</provider> </provider>
<service
android:name="com.tangem.tap.common.pushes.TangemPushNotificationService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application> </application>
</manifest> </manifest>

View file

@ -1,8 +1,32 @@
package com.tangem.tap package com.tangem.tap
import android.content.Intent import android.content.Intent
import android.hardware.biometrics.BiometricManager
import android.os.Build
import android.provider.Settings
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
interface ActivityResultCaller { interface ActivityResultCaller {
val activityResultLauncher: ActivityResultLauncher<Intent>? val activityResultLauncher: ActivityResultLauncher<Intent>?
} }
internal fun ActivityResultCaller.openSystemBiometrySettings() {
val settingsAction = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
Settings.ACTION_BIOMETRIC_ENROLL
}
else -> {
Settings.ACTION_SECURITY_SETTINGS
}
}
val intent = Intent(settingsAction).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
putExtra(
Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
BiometricManager.Authenticators.BIOMETRIC_STRONG,
)
}
}
activityResultLauncher?.launch(intent)
}

View file

@ -2,8 +2,11 @@ package com.tangem.tap
import com.tangem.TangemSdkLogger import com.tangem.TangemSdkLogger
import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.connection.NetworkConnectionManager
@ -15,6 +18,7 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.domain.feedback.GetFeedbackEmailUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
@ -26,9 +30,8 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
@ -59,8 +62,6 @@ interface ApplicationEntryPoint {
fun getWalletConnectSessionsRepository(): WalletConnectSessionsRepository fun getWalletConnectSessionsRepository(): WalletConnectSessionsRepository
fun getManageTokensFeatureToggles(): ManageTokensFeatureToggles
fun getScanCardProcessor(): ScanCardProcessor fun getScanCardProcessor(): ScanCardProcessor
fun getAppCurrencyRepository(): AppCurrencyRepository fun getAppCurrencyRepository(): AppCurrencyRepository
@ -109,5 +110,13 @@ interface ApplicationEntryPoint {
fun getDetailsFeatureToggles(): DetailsFeatureToggles fun getDetailsFeatureToggles(): DetailsFeatureToggles
fun getDetailsEntryPoint(): DetailsEntryPoint fun getGetCardInfoUseCase(): GetCardInfoUseCase
fun getUrlOpener(): UrlOpener
fun getShareManager(): ShareManager
fun getAppRouter(): AppRouter
fun getPushNotificationsFeatureToggles(): PushNotificationsFeatureToggles
} }

View file

@ -0,0 +1,71 @@
package com.tangem.tap
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.message.EventMessageEffect
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.utils.Provider
import dagger.hilt.android.AndroidEntryPoint
import java.util.WeakHashMap
import javax.inject.Inject
@AndroidEntryPoint
internal class DecomposeFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
private val component by lazy(mode = LazyThreadSafetyMode.NONE) {
val tag = requireArguments().getString(TAG_KEY)
val builder = componentsBuilders[tag]
requireNotNull(builder?.build()) {
"Component builder is not set, call newInstance() for DecomposeFragment creation first."
}
}
@Composable
override fun ScreenContent(modifier: Modifier) {
component.Content(modifier)
EventMessageEffect(
messageHandler = uiDependencies.eventMessageHandler,
snackbarHostState = uiDependencies.globalSnackbarHostState,
)
}
private class ComponentBuilder<C : ComposableContentComponent, P : Any, F : ComponentFactory<P, C>>(
private val contextProvider: Provider<AppComponentContext>,
private val params: P,
private val componentFactory: F,
) {
fun build(): C = componentFactory.create(contextProvider(), params)
}
companion object {
private const val TAG_KEY = "tag"
private val componentsBuilders = WeakHashMap<String, ComponentBuilder<*, *, *>>()
fun <C : ComposableContentComponent, P : Any, F : ComponentFactory<P, C>> newInstance(
tag: String,
contextProvider: Provider<AppComponentContext>,
params: P,
componentFactory: F,
): Fragment {
this@Companion.componentsBuilders[tag] = ComponentBuilder(contextProvider, params, componentFactory)
return DecomposeFragment().apply {
this.arguments = bundleOf(TAG_KEY to tag)
}
}
}
}

View file

@ -3,13 +3,12 @@ package com.tangem.tap
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.tangem.core.navigation.AppScreen import com.tangem.common.routing.AppRoute
import com.tangem.core.navigation.NavigationAction import com.tangem.common.routing.utils.popTo
import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchWithMain
import kotlinx.coroutines.* import kotlinx.coroutines.*
import timber.log.Timber import timber.log.Timber
import kotlin.time.Duration import kotlin.time.Duration
@ -50,7 +49,7 @@ internal class LockUserWalletsTimer(
start() start()
if (shouldOpenWelcomeScreenOnResume) { if (shouldOpenWelcomeScreenOnResume) {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false) settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false)
} }
} }
@ -128,7 +127,7 @@ internal class LockUserWalletsTimer(
if (wasApplicationStopped) { if (wasApplicationStopped) {
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true) settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
} else { } else {
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Welcome)) store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
} }
} }
} }

View file

@ -1,40 +1,41 @@
package com.tangem.tap package com.tangem.tap
import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.nfc.NfcAdapter import android.nfc.NfcAdapter
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import arrow.core.getOrElse import arrow.core.getOrElse
import by.kirich1409.viewbindingdelegate.viewBinding import by.kirich1409.viewbindingdelegate.viewBinding
import com.arkivanov.decompose.value.observe
import com.arkivanov.essenty.lifecycle.asEssentyLifecycle
import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.entity.SerializableIntent
import com.tangem.core.analytics.api.AnalyticsEventHandler 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.deeplink.DeepLinksRegistry
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.email.EmailSender import com.tangem.core.navigation.email.EmailSender
import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
@ -43,6 +44,7 @@ import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.data.card.sdk.CardSdkOwner import com.tangem.data.card.sdk.CardSdkOwner
import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
@ -50,8 +52,9 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.features.wallet.navigation.WalletRouter
@ -62,6 +65,9 @@ import com.tangem.tap.common.DialogManager
import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.OnActivityResultCallback
import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss
import com.tangem.tap.common.redux.NotificationsHandler import com.tangem.tap.common.redux.NotificationsHandler
import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
@ -71,17 +77,17 @@ import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHan
import com.tangem.tap.features.main.MainViewModel import com.tangem.tap.features.main.MainViewModel
import com.tangem.tap.features.main.model.Toast import com.tangem.tap.features.main.model.Toast
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphAction import com.tangem.tap.proxy.redux.DaggerGraphAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.routing.RoutingComponent
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R import com.tangem.wallet.R
import com.tangem.wallet.databinding.ActivityMainBinding import com.tangem.wallet.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import java.lang.ref.WeakReference
import javax.inject.Inject import javax.inject.Inject
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -128,9 +134,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject @Inject
lateinit var tokenDetailsRouter: TokenDetailsRouter lateinit var tokenDetailsRouter: TokenDetailsRouter
@Inject
lateinit var manageTokensUi: ManageTokensUi
@Inject @Inject
lateinit var walletConnectInteractor: WalletConnectInteractor lateinit var walletConnectInteractor: WalletConnectInteractor
@ -161,6 +164,28 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject @Inject
lateinit var emailSender: EmailSender lateinit var emailSender: EmailSender
@Inject
lateinit var stakingRouter: StakingRouter
@Inject
@RootAppComponentContext
internal lateinit var rootComponentContext: AppComponentContext
@Inject
internal lateinit var appRouterConfig: AppRouterConfig
@Inject
internal lateinit var routingComponentFactory: RoutingComponent.Factory
@Inject
internal lateinit var appRouter: AppRouter
@Inject
lateinit var pushNotificationsRouter: PushNotificationsRouter
@Inject
lateinit var cardRepository: CardRepository
internal val viewModel: MainViewModel by viewModels() internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode> private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
@ -180,6 +205,13 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
val splashScreen = installSplashScreen() val splashScreen = installSplashScreen()
enableEdgeToEdge(
navigationBarStyle = SystemBarStyle.auto(
Color.Transparent.toArgb(),
Color.Transparent.toArgb(),
),
)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
splashScreen.setKeepOnScreenCondition { viewModel.isSplashScreenShown } splashScreen.setKeepOnScreenCondition { viewModel.isSplashScreenShown }
@ -188,9 +220,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
observeAppThemeModeUpdates() observeAppThemeModeUpdates()
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
installRouting()
initContent() initContent()
checkForNotificationPermission()
observeStateUpdates() observeStateUpdates()
observePolkadotAccountHealthCheck() observePolkadotAccountHealthCheck()
@ -199,6 +231,31 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
} }
} }
private fun installRouting() {
val routingComponent = routingComponentFactory.create(
context = rootComponentContext,
)
appRouterConfig.routerScope = lifecycleScope
appRouterConfig.componentRouter = routingComponent.router
routingComponent.stack.observe(lifecycle.asEssentyLifecycle()) { childStack ->
appRouterConfig.stack = childStack.backStack
.plus(childStack.active)
.map { it.configuration }
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)
}
}
}
}
private fun observeStateUpdates() { private fun observeStateUpdates() {
viewModel.state viewModel.state
.flowWithLifecycle(lifecycle) .flowWithLifecycle(lifecycle)
@ -220,8 +277,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
} }
private fun installActivityDependencies() { private fun installActivityDependencies() {
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
cardSdkOwner.register(activity = this) cardSdkOwner.register(activity = this)
tangemSdkManager = injectedTangemSdkManager tangemSdkManager = injectedTangemSdkManager
appStateHolder.tangemSdkManager = tangemSdkManager appStateHolder.tangemSdkManager = tangemSdkManager
@ -241,11 +296,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
walletRouter = walletRouter, walletRouter = walletRouter,
walletConnectInteractor = walletConnectInteractor, walletConnectInteractor = walletConnectInteractor,
tokenDetailsRouter = tokenDetailsRouter, tokenDetailsRouter = tokenDetailsRouter,
manageTokensUi = manageTokensUi,
cardSdkConfigRepository = cardSdkConfigRepository, cardSdkConfigRepository = cardSdkConfigRepository,
sendRouter = sendRouter, sendRouter = sendRouter,
qrScanningRouter = qrScanningRouter, qrScanningRouter = qrScanningRouter,
emailSender = emailSender, emailSender = emailSender,
stakingRouter = stakingRouter,
pushNotificationsRouter = pushNotificationsRouter,
), ),
) )
} }
@ -266,8 +322,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@SuppressLint("SourceLockedOrientationActivity") @SuppressLint("SourceLockedOrientationActivity")
private fun initContent() { private fun initContent() {
WindowCompat.setDecorFitsSystemWindows(window, false)
supportFragmentManager.registerFragmentLifecycleCallbacks( supportFragmentManager.registerFragmentLifecycleCallbacks(
NavBarInsetsFragmentLifecycleCallback(), NavBarInsetsFragmentLifecycleCallback(),
true, true,
@ -298,19 +352,21 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val nfcAdapter = NfcAdapter.getDefaultAdapter(this) val nfcAdapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)
val pendingIntent = PendingIntent.getActivity( if (nfcAdapter?.isEnabled == true) {
this, val pendingIntent = PendingIntent.getActivity(
0, this,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0,
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
) PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
val intentFilters = arrayOf( )
IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED), val intentFilters = arrayOf(
IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED), IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED),
IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED), IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED),
) IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED),
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null) )
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null)
}
// TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop // TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop
notificationsHandler = NotificationsHandler(binding.fragmentContainer) notificationsHandler = NotificationsHandler(binding.fragmentContainer)
@ -319,8 +375,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
val nfcAdapter = NfcAdapter.getDefaultAdapter(this) val nfcAdapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)
nfcAdapter.disableForegroundDispatch(this)
if (nfcAdapter?.isEnabled == true) {
nfcAdapter.disableForegroundDispatch(this)
}
} }
override fun onStop() { override fun onStop() {
@ -330,7 +389,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
} }
override fun onDestroy() { override fun onDestroy() {
store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this)))
intentProcessor.removeAll() intentProcessor.removeAll()
super.onDestroy() super.onDestroy()
} }
@ -470,8 +528,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
} }
private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) { private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) {
val backStack = store.state.navigationState.backStack val backStack = appRouter.stack
val isOnInitialScreen = backStack.all { it == AppScreen.Welcome || it == AppScreen.Home } val isOnInitialScreen = backStack.all { it is AppRoute.Welcome || it is AppRoute.Home }
val isNotScannedBefore = store.state.globalState.scanResponse == null val isNotScannedBefore = store.state.globalState.scanResponse == null
val isOnboardingServiceNotActive = !store.state.globalState.onboardingState.onboardingStarted val isOnboardingServiceNotActive = !store.state.globalState.onboardingState.onboardingStarted
@ -488,17 +546,16 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) { private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) { if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
store.dispatch( store.dispatchNavigationAction {
NavigationAction.NavigateTo( replaceAll(AppRoute.Welcome(intentWhichStartedActivity?.let(::SerializableIntent)))
screen = AppScreen.Welcome, }
bundle = intentWhichStartedActivity?.let {
bundleOf(WelcomeFragment.INITIAL_INTENT_KEY to it)
},
),
)
} else { } else {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
lifecycleScope.launch { lifecycleScope.launch {
val toggles = store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles)
val isEnabled = toggles.isPushNotificationsEnabled && !cardRepository.isTangemTOSAccepted()
val route = if (isEnabled) AppRoute.Disclaimer(isTosAccepted = false) else AppRoute.Home
store.dispatchNavigationAction { replaceAll(route) }
intentProcessor.handleIntent(intentWhichStartedActivity, false) intentProcessor.handleIntent(intentWhichStartedActivity, false)
} }
} }
@ -506,16 +563,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
store.dispatch(BackupAction.CheckForUnfinishedBackup) store.dispatch(BackupAction.CheckForUnfinishedBackup)
} }
private fun checkForNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
BuildConfig.LOG_ENABLED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) !=
PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0)
}
}
private fun observePolkadotAccountHealthCheck() { private fun observePolkadotAccountHealthCheck() {
lifecycleScope.launch { lifecycleScope.launch {
getPolkadotCheckHasResetUseCase() getPolkadotCheckHasResetUseCase()

View file

@ -13,6 +13,7 @@ import com.tangem.LogFormat
import com.tangem.TangemSdkLogger import com.tangem.TangemSdkLogger
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
@ -32,6 +33,7 @@ import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.common.LogConfig import com.tangem.domain.common.LogConfig
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.domain.feedback.GetFeedbackEmailUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
@ -43,9 +45,8 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
@ -53,7 +54,7 @@ import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHand
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
import com.tangem.tap.common.chat.ChatManager import com.tangem.tap.common.chat.ChatManager
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.feedback.LegacyFeedbackManager
import com.tangem.tap.common.images.createCoilImageLoader import com.tangem.tap.common.images.createCoilImageLoader
import com.tangem.tap.common.log.TangemLogCollector import com.tangem.tap.common.log.TangemLogCollector
import com.tangem.tap.common.log.TimberFormatStrategy import com.tangem.tap.common.log.TimberFormatStrategy
@ -110,9 +111,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val walletConnectSessionsRepository: WalletConnectSessionsRepository private val walletConnectSessionsRepository: WalletConnectSessionsRepository
get() = entryPoint.getWalletConnectSessionsRepository() get() = entryPoint.getWalletConnectSessionsRepository()
private val manageTokensFeatureToggles: ManageTokensFeatureToggles
get() = entryPoint.getManageTokensFeatureToggles()
private val scanCardProcessor: ScanCardProcessor private val scanCardProcessor: ScanCardProcessor
get() = entryPoint.getScanCardProcessor() get() = entryPoint.getScanCardProcessor()
@ -181,13 +179,25 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase
get() = entryPoint.getSaveBlockchainErrorUseCase() get() = entryPoint.getSaveBlockchainErrorUseCase()
// endregion
private val getCardInfoUseCase: GetCardInfoUseCase
get() = entryPoint.getGetCardInfoUseCase()
private val detailsFeatureToggles: DetailsFeatureToggles private val detailsFeatureToggles: DetailsFeatureToggles
get() = entryPoint.getDetailsFeatureToggles() get() = entryPoint.getDetailsFeatureToggles()
private val detailsEntryPoint: DetailsEntryPoint private val urlOpener
get() = entryPoint.getDetailsEntryPoint() get() = entryPoint.getUrlOpener()
private val shareManager
get() = entryPoint.getShareManager()
private val appRouter: AppRouter
get() = entryPoint.getAppRouter()
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles
get() = entryPoint.getPushNotificationsFeatureToggles()
// endregion
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -253,7 +263,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
customTokenFeatureToggles = customTokenFeatureToggles, customTokenFeatureToggles = customTokenFeatureToggles,
walletConnectRepository = walletConnect2Repository, walletConnectRepository = walletConnect2Repository,
walletConnectSessionsRepository = walletConnectSessionsRepository, walletConnectSessionsRepository = walletConnectSessionsRepository,
manageTokensFeatureToggles = manageTokensFeatureToggles,
scanCardProcessor = scanCardProcessor, scanCardProcessor = scanCardProcessor,
appCurrencyRepository = appCurrencyRepository, appCurrencyRepository = appCurrencyRepository,
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
@ -274,9 +283,14 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
settingsRepository = settingsRepository, settingsRepository = settingsRepository,
blockchainSDKFactory = blockchainSDKFactory, blockchainSDKFactory = blockchainSDKFactory,
saveBlockchainErrorUseCase = saveBlockchainErrorUseCase, saveBlockchainErrorUseCase = saveBlockchainErrorUseCase,
detailsFeatureToggles = detailsFeatureToggles, getFeedbackEmailUseCase = getFeedbackEmailUseCase,
detailsEntryPoint = detailsEntryPoint, getCardInfoUseCase = getCardInfoUseCase,
assetLoader = assetLoader, assetLoader = assetLoader,
detailsFeatureToggles = detailsFeatureToggles,
urlOpener = urlOpener,
shareManager = shareManager,
appRouter = appRouter,
pushNotificationsFeatureToggles = pushNotificationsFeatureToggles,
), ),
), ),
) )
@ -365,7 +379,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
logger = if (feedbackManagerFeatureToggles.isLocalLogsEnabled) tangemSdkLogger else tangemLogCollector, logger = if (feedbackManagerFeatureToggles.isLocalLogsEnabled) tangemSdkLogger else tangemLogCollector,
) )
val feedbackManager = FeedbackManager( val feedbackManager = LegacyFeedbackManager(
infoHolder = additionalFeedbackInfo, infoHolder = additionalFeedbackInfo,
logCollector = tangemLogCollector, logCollector = tangemLogCollector,
chatManager = ChatManager(foregroundActivityObserver), chatManager = ChatManager(foregroundActivityObserver),

View file

@ -2,7 +2,7 @@ package com.tangem.tap.common
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import com.tangem.core.navigation.StateDialog import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.ui.* import com.tangem.tap.common.ui.*
@ -46,7 +46,11 @@ class DialogManager : StoreSubscriber<GlobalState> {
dialog = when (state.dialog) { dialog = when (state.dialog) {
is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context) is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context)
is StateDialog.ScanFailsDialog -> ScanFailsDialog.create(context, state.dialog.source) is StateDialog.ScanFailsDialog -> ScanFailsDialog.create(
context = context,
source = state.dialog.source,
onTryAgain = state.dialog.onTryAgain,
)
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context) is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
is AppDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog( is AppDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog(

View file

@ -0,0 +1,17 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
class ScanFailsDialogAnalytics(button: Buttons, source: AnalyticsParam.ScreensSources) : AnalyticsEvent(
category = "Cant Scan The Card",
event = button.event,
params = mapOf(
AnalyticsParam.SOURCE to source.value,
),
) {
enum class Buttons(val event: String) {
TRY_AGAIN("Try again button"),
HOW_TO_SCAN("Button blog"),
}
}

View file

@ -1,171 +1,51 @@
package com.tangem.tap.common.extensions package com.tangem.tap.common.extensions
import android.os.Bundle import androidx.fragment.app.DialogFragment
import androidx.fragment.app.* import androidx.fragment.app.Fragment
import com.tangem.core.navigation.AppScreen import androidx.fragment.app.FragmentManager
import com.tangem.core.navigation.FragmentShareTransition import com.tangem.utils.Provider
import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment
import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorFragment
import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryFragment
import com.tangem.tap.features.details.ui.details.DetailsFragment
import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment
import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
import com.tangem.tap.features.home.HomeFragment
import com.tangem.tap.features.main.ui.ModalNotificationBottomSheetFragment
import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment
import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragment
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.tokens.impl.presentation.TokensListFragment
import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.wallet.R import com.tangem.wallet.R
import timber.log.Timber import timber.log.Timber
fun FragmentActivity.openFragment( fun FragmentManager.showFragmentAllowingStateLoss(name: String, fragmentProvider: Provider<Fragment>) {
screen: AppScreen, Timber.d("Showing $name route")
addToBackstack: Boolean,
bundle: Bundle? = null,
fgShareTransition: FragmentShareTransition? = null,
) {
val transaction = supportFragmentManager.beginTransaction().apply {
setReorderingAllowed(true)
}
val fragment = fragmentFactory(screen).apply { val isPoppedBack = popBackStackImmediate(name, 0)
arguments = bundle
if (fgShareTransition != null) { if (!isPoppedBack) {
sharedElementEnterTransition = fgShareTransition.enterTransitionSet val fragment = fragmentProvider()
sharedElementReturnTransition = fgShareTransition.exitTransitionSet
fgShareTransition.shareElements.forEach { shareElement -> if (fragment is DialogFragment) {
shareElement.wView.get()?.let { view -> fragment.showDialog(fragmentManager = this, name)
transaction.addSharedElement(view, shareElement.elementName) } else {
} fragment.showFragment(fragmentManager = this, name)
}
} }
}
if (screen.isDialogFragment && fragment is DialogFragment) { Timber.d("Route $name is shown")
fragment.showAllowingStateLoss(
fragmentManager = supportFragmentManager,
baseTransaction = transaction,
tag = screen.name,
addToBackstack = addToBackstack,
)
} else { } else {
transaction.replace(R.id.fragment_container, fragment, screen.name) Timber.d("Route $name is found in backstack and shown")
if (addToBackstack) { }
transaction.addToBackStack(screen.name) }
}
private fun DialogFragment.showDialog(fragmentManager: FragmentManager, name: String) {
val transaction = fragmentManager.beginTransaction()
try {
transaction.addToBackStack(name)
show(transaction, name)
} catch (e: IllegalStateException) {
transaction.add(this, name)
transaction.addToBackStack(name)
transaction.commitAllowingStateLoss() transaction.commitAllowingStateLoss()
} }
} }
private fun DialogFragment.showAllowingStateLoss( private fun Fragment.showFragment(fragmentManager: FragmentManager, name: String) {
fragmentManager: FragmentManager, val transaction = fragmentManager.beginTransaction()
baseTransaction: FragmentTransaction,
tag: String,
addToBackstack: Boolean,
) {
runCatching {
if (addToBackstack) baseTransaction.addToBackStack(tag)
show(baseTransaction, tag)
}
.onFailure { throwable ->
if (throwable is IllegalStateException) {
val transaction = fragmentManager.beginTransaction()
transaction.add(this, tag)
if (addToBackstack) transaction.addToBackStack(tag)
transaction.commitAllowingStateLoss() transaction.replace(R.id.fragment_container, this, name)
} else { transaction.addToBackStack(name)
Timber.e(throwable)
} transaction.commitAllowingStateLoss()
}
}
fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) {
val inclusiveFlag = if (inclusive) FragmentManager.POP_BACK_STACK_INCLUSIVE else 0
try {
this.supportFragmentManager.popBackStack(screen?.name, inclusiveFlag)
} catch (e: IllegalStateException) {
Timber.e(e)
}
}
fun FragmentActivity.getPreviousScreen(): AppScreen? {
val indexOfLastFragment = if (this.supportFragmentManager.backStackEntryCount > 0) {
this.supportFragmentManager.backStackEntryCount - 1
} else {
0
}
val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount) {
this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name
} else {
null
}
return tag?.let { AppScreen.valueOf(tag) }
}
@Suppress("ComplexMethod", "LongMethod")
private fun fragmentFactory(screen: AppScreen): Fragment {
return when (screen) {
AppScreen.Home -> HomeFragment()
AppScreen.OnboardingNote -> OnboardingNoteFragment()
AppScreen.OnboardingWallet -> OnboardingWalletFragment()
AppScreen.OnboardingTwins -> OnboardingTwinsFragment()
AppScreen.OnboardingOther -> OnboardingOtherCardsFragment()
AppScreen.Wallet -> {
store.inject(getDependency = DaggerGraphState::walletRouter).getEntryFragment()
}
AppScreen.Send -> {
val featureToggles = store.inject(getDependency = DaggerGraphState::sendFeatureToggles)
if (featureToggles.isRedesignedSendEnabled) {
store.inject(getDependency = DaggerGraphState::sendRouter).getEntryFragment()
} else {
SendFragment()
}
}
AppScreen.Details -> {
val featureToggles = store.inject(getDependency = DaggerGraphState::detailsFeatureToggles)
if (featureToggles.isRedesignEnabled) {
store.inject(DaggerGraphState::detailsEntryPoint).entryFragment()
} else {
DetailsFragment()
}
}
AppScreen.DetailsSecurity -> SecurityModeFragment()
AppScreen.CardSettings -> CardSettingsFragment()
AppScreen.AppSettings -> AppSettingsFragment()
AppScreen.ResetToFactory -> ResetCardFragment()
AppScreen.AccessCodeRecovery -> AccessCodeRecoveryFragment()
AppScreen.Disclaimer -> DisclaimerFragment()
AppScreen.ManageTokens -> TokensListFragment()
AppScreen.AddCustomToken -> AddCustomTokenFragment()
AppScreen.WalletDetails -> {
store.inject(getDependency = DaggerGraphState::tokenDetailsRouter).getEntryFragment()
}
AppScreen.WalletConnectSessions -> WalletConnectFragment()
AppScreen.QrScanning -> {
store.inject(getDependency = DaggerGraphState::qrScanningRouter).getEntryFragment()
}
AppScreen.ReferralProgram -> ReferralFragment()
AppScreen.Swap -> SwapFragment()
AppScreen.Welcome -> WelcomeFragment()
AppScreen.SaveWallet -> SaveWalletBottomSheetFragment()
AppScreen.AppCurrencySelector -> AppCurrencySelectorFragment()
AppScreen.ModalNotification -> ModalNotificationBottomSheetFragment()
}
} }

View file

@ -1,8 +1,8 @@
package com.tangem.tap.common.extensions package com.tangem.tap.common.extensions
import com.tangem.core.navigation.NavigationAction import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.StateDialog
import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
@ -82,12 +82,16 @@ suspend fun dispatchOnMain(vararg actions: Action) {
withMainContext { actions.forEach { store.dispatch(it) } } withMainContext { actions.forEach { store.dispatch(it) } }
} }
fun Store<*>.dispatchOpenUrl(url: String) { fun Store<AppState>.dispatchOpenUrl(url: String) {
dispatch(NavigationAction.OpenUrl(url)) inject(DaggerGraphState::urlOpener).openUrl(url)
} }
fun Store<*>.dispatchShare(url: String) { fun Store<AppState>.dispatchShare(url: String) {
dispatch(NavigationAction.Share(url)) inject(DaggerGraphState::shareManager).shareText(url)
}
fun Store<AppState>.dispatchNavigationAction(action: AppRouter.() -> Unit) {
inject(DaggerGraphState::appRouter).action()
} }
inline fun <reified T> Store<AppState>.inject(getDependency: DaggerGraphState.() -> T?): T { inline fun <reified T> Store<AppState>.inject(getDependency: DaggerGraphState.() -> T?): T {

View file

@ -3,7 +3,10 @@
package com.tangem.tap.common.extensions package com.tangem.tap.common.extensions
import android.app.Activity import android.app.Activity
import android.content.* import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.ContextWrapper
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.View import android.view.View
import androidx.annotation.* import androidx.annotation.*
@ -78,16 +81,6 @@ fun Context.getFromClipboard(default: CharSequence? = null): CharSequence? {
return clipData.getItemAt(0).text return clipData.getItemAt(0).text
} }
fun Context.shareText(text: String) {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, text)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
startActivity(shareIntent)
}
fun View.getString(resId: Int, vararg formatArgs: Any?): String { fun View.getString(resId: Int, vararg formatArgs: Any?): String {
return context.getString(resId, *formatArgs) return context.getString(resId, *formatArgs)
} }

View file

@ -7,13 +7,14 @@ import com.tangem.domain.common.TapWorkarounds
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.domain.feedback.GetFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.chat.ChatManager import com.tangem.tap.common.chat.ChatManager
import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.sendEmail import com.tangem.tap.common.extensions.sendEmail
import com.tangem.tap.common.log.TangemLogCollector import com.tangem.tap.common.log.TangemLogCollector
import com.tangem.tap.foregroundActivityObserver import com.tangem.tap.foregroundActivityObserver
import com.tangem.tap.mainScope
import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.tap.withForegroundActivity import com.tangem.tap.withForegroundActivity
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -25,7 +26,7 @@ import java.io.StringWriter
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
class FeedbackManager( class LegacyFeedbackManager(
val infoHolder: AdditionalFeedbackInfo, val infoHolder: AdditionalFeedbackInfo,
private val logCollector: TangemLogCollector, private val logCollector: TangemLogCollector,
private val chatManager: ChatManager, private val chatManager: ChatManager,
@ -36,16 +37,24 @@ class FeedbackManager(
private var sessionFeedbackFile: File? = null private var sessionFeedbackFile: File? = null
private var sessionLogsFile: File? = null private var sessionLogsFile: File? = null
fun sendEmail(feedbackData: FeedbackData, onFail: ((Exception) -> Unit)? = null) { fun sendEmail(feedbackData: FeedbackData, scanResponse: ScanResponse?) {
if (feedbackManagerFeatureToggles.isLocalLogsEnabled) { if (feedbackManagerFeatureToggles.isLocalLogsEnabled) {
mainScope.launch { scope.launch {
val getCardInfo = suspend {
scanResponse ?: error("ScanResponse must be not null")
store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull()
?: error("CardInfo must be not null")
}
val email = getFeedbackEmailUseCase( val email = getFeedbackEmailUseCase(
when (feedbackData) { type = when (feedbackData) {
is FeedbackEmail -> FeedbackEmailType.DirectUserRequest is FeedbackEmail -> FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo())
is RateCanBeBetterEmail -> FeedbackEmailType.RateCanBeBetter is RateCanBeBetterEmail -> FeedbackEmailType.RateCanBeBetter(cardInfo = getCardInfo())
is ScanFailsEmail -> FeedbackEmailType.ScanningProblem is ScanFailsEmail -> FeedbackEmailType.ScanningProblem
is SendTransactionFailedEmail -> FeedbackEmailType.TransactionSendingProblem is SendTransactionFailedEmail -> {
else -> FeedbackEmailType.DirectUserRequest FeedbackEmailType.TransactionSendingProblem(cardInfo = getCardInfo())
}
else -> FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo())
}, },
) )
@ -66,12 +75,28 @@ class FeedbackManager(
subject = activity.getString(feedbackData.subjectResId), subject = activity.getString(feedbackData.subjectResId),
message = feedbackData.joinTogether(activity, infoHolder), message = feedbackData.joinTogether(activity, infoHolder),
file = getLogFile(activity), file = getLogFile(activity),
onFail = onFail,
) )
} }
} }
} }
fun sendEmail(type: FeedbackEmailType) {
if (!feedbackManagerFeatureToggles.isLocalLogsEnabled) error("LOCAL_LOGS feature toggle must be enabled")
scope.launch {
val email = getFeedbackEmailUseCase(type = type)
store.inject(DaggerGraphState::emailSender).send(
email = EmailSender.Email(
address = email.address,
subject = email.subject,
message = email.message,
attachment = email.file,
),
)
}
}
fun openChat(config: ChatConfig, feedbackData: FeedbackData) { fun openChat(config: ChatConfig, feedbackData: FeedbackData) {
chatManager.open( chatManager.open(
config = config, config = config,
@ -144,7 +169,7 @@ class FeedbackManager(
} }
} }
companion object { private companion object {
const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com" const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com" const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
const val FEEDBACK_FILE = "feedback.txt" const val FEEDBACK_FILE = "feedback.txt"

View file

@ -0,0 +1,20 @@
package com.tangem.tap.common.feedback
import com.tangem.domain.feedback.FeedbackManager
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.tap.store
import timber.log.Timber
internal class ProxyFeedbackManager : FeedbackManager {
override fun sendEmail(type: FeedbackEmailType) {
val manager = store.state.globalState.feedbackManager
if (manager == null) {
Timber.e("Feedback manager is not initialized")
return
}
manager.sendEmail(type)
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.tap.common.finisher
import android.content.Context
import android.content.Intent
import com.tangem.core.navigation.finisher.AppFinisher
import com.tangem.tap.MainActivity
import com.tangem.tap.foregroundActivityObserver
import com.tangem.tap.withForegroundActivity
internal class AndroidAppFinisher(
private val appContext: Context,
) : AppFinisher {
override fun finish() {
foregroundActivityObserver.withForegroundActivity { activity ->
activity.finish()
}
}
override fun restart() {
val intent = Intent(appContext, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
appContext.startActivity(intent)
}
}

View file

@ -0,0 +1,103 @@
package com.tangem.tap.common.pushes
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import coil.executeBlocking
import coil.request.ImageRequest
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.tangem.domain.common.LogConfig
import com.tangem.tap.MainActivity
import com.tangem.tap.common.images.createCoilImageLoader
import com.tangem.wallet.R
import timber.log.Timber
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
internal class TangemPushNotificationService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
Timber.d("New FCM token received: $token")
}
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val notification = message.notification ?: return
val channelId = notification.channelId ?: TANGEM_CHANNEL_ID
// TODO refactoring: [REDACTED_JIRA]
val intent = Intent(applicationContext, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(
/* context = */ this,
/* requestCode = */ PUSH_NOTIFICATION_REQUEST_CODE,
/* intent = */ intent,
/* flags = */ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
)
val notificationBuilder =
NotificationCompat.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.ic_tangem_24)
.setContentTitle(notification.title)
.setContentText(notification.body)
.setPriority(message.priority)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setVibrate(notification.vibrateTimings)
.apply {
notification.imageUrl?.let { uri ->
val bitmap = getBitmapImageFromUrl(uri)
setStyle(
NotificationCompat
.BigPictureStyle()
.bigPicture(bitmap),
).setLargeIcon(bitmap)
}
}
val notificationManager = applicationContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel = NotificationChannel(
channelId,
ContextCompat.getString(applicationContext, R.string.tangem_app_name),
NotificationManager.IMPORTANCE_HIGH,
)
notificationManager.createNotificationChannel(notificationChannel)
}
// Generating unique notification id
val uniqueId = (System.currentTimeMillis() % Integer.MAX_VALUE).toInt()
notificationManager.notify(
/* id = */ uniqueId,
/* notification = */ notificationBuilder.build(),
)
}
private fun getBitmapImageFromUrl(url: Uri): Bitmap? {
return createCoilImageLoader(
applicationContext,
logEnabled = LogConfig.imageLoader,
).executeBlocking(
ImageRequest.Builder(applicationContext)
.data(url)
.build(),
).drawable?.toBitmap()
}
private companion object {
const val TANGEM_CHANNEL_ID = "Tangem General" // General channel for notifications
const val PUSH_NOTIFICATION_REQUEST_CODE = 123
}
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.common.redux package com.tangem.tap.common.redux
import com.tangem.common.extensions.VoidCallback import com.tangem.common.extensions.VoidCallback
import com.tangem.core.navigation.StateDialog import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.TestAction import com.tangem.tap.common.TestAction
import com.tangem.tap.domain.model.Currency import com.tangem.tap.domain.model.Currency
import com.tangem.tap.domain.model.WalletAddressData import com.tangem.tap.domain.model.WalletAddressData

View file

@ -1,7 +1,6 @@
package com.tangem.tap.common.redux package com.tangem.tap.common.redux
import com.tangem.tap.common.redux.global.globalReducer import com.tangem.tap.common.redux.global.globalReducer
import com.tangem.tap.common.redux.navigation.NavigationReducer
import com.tangem.tap.features.details.redux.DetailsReducer import com.tangem.tap.features.details.redux.DetailsReducer
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
@ -23,7 +22,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
if (action is AppAction.RestoreState) return action.state if (action is AppAction.RestoreState) return action.state
return AppState( return AppState(
navigationState = NavigationReducer.reduce(action, state),
globalState = globalReducer(action, state, appStateHolder), globalState = globalReducer(action, state, appStateHolder),
homeState = HomeReducer.reduce(action, state), homeState = HomeReducer.reduce(action, state),
onboardingNoteState = OnboardingNoteReducer.reduce(action, state), onboardingNoteState = OnboardingNoteReducer.reduce(action, state),

View file

@ -1,13 +1,11 @@
package com.tangem.tap.common.redux package com.tangem.tap.common.redux
import com.tangem.core.navigation.NavigationState
import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.NetworkServices import com.tangem.domain.redux.global.NetworkServices
import com.tangem.tap.common.redux.global.GlobalMiddleware import com.tangem.tap.common.redux.global.GlobalMiddleware
import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.legacy.LegacyMiddleware import com.tangem.tap.common.redux.legacy.LegacyMiddleware
import com.tangem.tap.common.redux.navigation.navigationMiddleware
import com.tangem.tap.features.details.redux.DetailsMiddleware import com.tangem.tap.features.details.redux.DetailsMiddleware
import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
@ -40,7 +38,6 @@ import org.rekotlin.Middleware
import org.rekotlin.StateType import org.rekotlin.StateType
data class AppState( data class AppState(
val navigationState: NavigationState = NavigationState(),
val globalState: GlobalState = GlobalState(), val globalState: GlobalState = GlobalState(),
val homeState: HomeState = HomeState(), val homeState: HomeState = HomeState(),
val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(), val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(),
@ -73,7 +70,6 @@ data class AppState(
fun getMiddleware(): List<Middleware<AppState>> { fun getMiddleware(): List<Middleware<AppState>> {
return listOf( return listOf(
logMiddleware, logMiddleware,
navigationMiddleware,
notificationsMiddleware, notificationsMiddleware,
GlobalMiddleware.handler, GlobalMiddleware.handler,
HomeMiddleware.handler, HomeMiddleware.handler,

View file

@ -2,14 +2,14 @@ package com.tangem.tap.common.redux.global
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.navigation.StateDialog
import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.config.models.ChatConfig import com.tangem.datasource.config.models.ChatConfig
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.feedback.FeedbackData import com.tangem.tap.common.feedback.FeedbackData
import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.feedback.LegacyFeedbackManager
import com.tangem.tap.common.redux.DebugErrorAction import com.tangem.tap.common.redux.DebugErrorAction
import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.NotificationAction
@ -79,9 +79,9 @@ sealed class GlobalAction : Action {
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction() data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction() data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction() data class SetFeedbackManager(val feedbackManager: LegacyFeedbackManager) : GlobalAction()
data class SendEmail(val feedbackData: FeedbackData) : GlobalAction() data class SendEmail(val feedbackData: FeedbackData, val scanResponse: ScanResponse?) : GlobalAction()
data class OpenChat(val feedbackData: FeedbackData, val chatConfig: ChatConfig? = null) : GlobalAction() data class OpenChat(val feedbackData: FeedbackData, val chatConfig: ChatConfig? = null) : GlobalAction()
object ExchangeManager : GlobalAction() { object ExchangeManager : GlobalAction() {

View file

@ -4,12 +4,12 @@ import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard import com.tangem.common.extensions.guard
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.navigation.StateDialog
import com.tangem.datasource.config.models.Config import com.tangem.datasource.config.models.Config
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.LogConfig import com.tangem.domain.common.LogConfig
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.extensions.* import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.send.redux.SendAction
@ -69,7 +69,10 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
} }
} }
is GlobalAction.SendEmail -> { is GlobalAction.SendEmail -> {
store.state.globalState.feedbackManager?.sendEmail(action.feedbackData) store.state.globalState.feedbackManager?.sendEmail(
feedbackData = action.feedbackData,
scanResponse = action.scanResponse,
)
} }
is GlobalAction.OpenChat -> { is GlobalAction.OpenChat -> {
val globalState = store.state.globalState val globalState = store.state.globalState

View file

@ -1,11 +1,11 @@
package com.tangem.tap.common.redux.global package com.tangem.tap.common.redux.global
import com.tangem.core.navigation.StateDialog
import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.ConfigManager
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.feedback.LegacyFeedbackManager
import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.features.onboarding.OnboardingManager
@ -20,7 +20,7 @@ data class GlobalState(
val tapWalletManager: TapWalletManager = TapWalletManager(), val tapWalletManager: TapWalletManager = TapWalletManager(),
val configManager: ConfigManager? = null, val configManager: ConfigManager? = null,
val warningManager: WarningMessagesManager? = null, val warningManager: WarningMessagesManager? = null,
val feedbackManager: FeedbackManager? = null, val feedbackManager: LegacyFeedbackManager? = null,
val appCurrency: AppCurrency = AppCurrency.Default, val appCurrency: AppCurrency = AppCurrency.Default,
val scanCardFailsCounter: Int = 0, val scanCardFailsCounter: Int = 0,
val dialog: StateDialog? = null, val dialog: StateDialog? = null,

View file

@ -1,8 +1,11 @@
package com.tangem.tap.common.redux.legacy package com.tangem.tap.common.redux.legacy
import com.tangem.blockchain.common.AmountType
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.tokens.utils.convertToAmount import com.tangem.domain.tokens.utils.convertToAmount
import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.feedback.RateCanBeBetterEmail import com.tangem.tap.common.feedback.RateCanBeBetterEmail
import com.tangem.tap.common.feedback.SendTransactionFailedEmail import com.tangem.tap.common.feedback.SendTransactionFailedEmail
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
@ -19,7 +22,10 @@ internal object LegacyMiddleware {
{ action -> { action ->
when (action) { when (action) {
is LegacyAction.SendEmailRateCanBeBetter -> { is LegacyAction.SendEmailRateCanBeBetter -> {
store.state.globalState.feedbackManager?.sendEmail(RateCanBeBetterEmail()) store.state.globalState.feedbackManager?.sendEmail(
feedbackData = RateCanBeBetterEmail(),
scanResponse = action.scanResponse,
)
} }
is LegacyAction.StartOnboardingProcess -> { is LegacyAction.StartOnboardingProcess -> {
store.dispatch( store.dispatch(
@ -28,8 +34,28 @@ internal object LegacyMiddleware {
} }
is LegacyAction.SendEmailTransactionFailed -> { is LegacyAction.SendEmailTransactionFailed -> {
if (store.inject(DaggerGraphState::feedbackManagerFeatureToggles).isLocalLogsEnabled) { if (store.inject(DaggerGraphState::feedbackManagerFeatureToggles).isLocalLogsEnabled) {
val amount = action.amount?.convertToAmount(action.cryptoCurrency)
store.inject(DaggerGraphState::saveBlockchainErrorUseCase).invoke(
error = BlockchainErrorInfo(
errorMessage = action.errorMessage,
blockchainId = action.cryptoCurrency.network.id.value,
derivationPath = action.cryptoCurrency.network.derivationPath.value,
destinationAddress = action.destinationAddress.orEmpty(),
tokenSymbol = if (amount?.type is AmountType.Token) {
amount.currencySymbol
} else {
""
},
amount = amount?.value?.stripZeroPlainString() ?: "unknown",
fee = action.fee?.convertToAmount(action.cryptoCurrency)
?.value?.stripZeroPlainString() ?: "unknown",
),
)
store.state.globalState.feedbackManager?.sendEmail( store.state.globalState.feedbackManager?.sendEmail(
SendTransactionFailedEmail(action.errorMessage), feedbackData = SendTransactionFailedEmail(action.errorMessage),
scanResponse = action.scanResponse,
) )
} else { } else {
scope.launch { scope.launch {
@ -46,7 +72,8 @@ internal object LegacyMiddleware {
) )
} }
store.state.globalState.feedbackManager?.sendEmail( store.state.globalState.feedbackManager?.sendEmail(
SendTransactionFailedEmail(action.errorMessage), feedbackData = SendTransactionFailedEmail(action.errorMessage),
scanResponse = null,
) )
} }
} }

View file

@ -1,91 +0,0 @@
package com.tangem.tap.common.redux.navigation
import android.content.Intent
import android.hardware.biometrics.BiometricManager
import android.os.Build
import android.provider.Settings
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.tap.activityResultCaller
import com.tangem.tap.common.CustomTabsManager
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.store
import org.rekotlin.Middleware
val navigationMiddleware: Middleware<AppState> = { _, state ->
{ next ->
{ action ->
if (action is NavigationAction) {
val navState = state()?.navigationState
when (action) {
is NavigationAction.NavigateTo -> {
navState?.activity?.get()?.openFragment(
screen = action.screen,
addToBackstack = action.addToBackstack,
fgShareTransition = action.fragmentShareTransition,
bundle = action.bundle,
)
}
is NavigationAction.PopBackTo -> {
if (navState?.backStack?.lastOrNull() != action.screen) {
when (val screen = action.screen) {
AppScreen.Home,
AppScreen.Welcome,
-> {
if (navState?.backStack?.contains(screen) == false) {
// Pop back to activity
navState.activity?.get()?.popBackTo(screen = null, inclusive = true)
store.dispatchOnMain(NavigationAction.NavigateTo(screen))
} else {
navState?.activity?.get()?.popBackTo(screen, action.inclusive)
}
}
else -> {
navState?.activity?.get()?.popBackTo(screen, action.inclusive)
}
}
}
}
is NavigationAction.OpenUrl -> {
navState?.activity?.get()?.let {
CustomTabsManager().openUrl(action.url, it)
}
}
is NavigationAction.OpenDocument -> {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = action.url
navState?.activity?.get()?.startActivity(intent)
}
is NavigationAction.OpenDialog -> store.dispatchDialogShow(action.stateDialog)
is NavigationAction.OpenBiometricsSettings -> {
val settingsAction = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
Settings.ACTION_BIOMETRIC_ENROLL
}
else -> {
Settings.ACTION_SECURITY_SETTINGS
}
}
val intent = Intent(settingsAction).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
putExtra(
Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
BiometricManager.Authenticators.BIOMETRIC_STRONG,
)
}
}
activityResultCaller.activityResultLauncher?.launch(intent)
}
is NavigationAction.Share -> {
navState?.activity?.get()?.shareText(action.data)
}
is NavigationAction.ActivityCreated,
is NavigationAction.ActivityDestroyed,
-> Unit
}
}
next(action)
}
}
}

View file

@ -1,39 +0,0 @@
package com.tangem.tap.common.redux.navigation
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.NavigationState
import com.tangem.tap.common.extensions.getPreviousScreen
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action
object NavigationReducer {
fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state)
}
private fun internalReduce(action: Action, state: AppState): NavigationState {
val navigationAction = action as? NavigationAction ?: return state.navigationState
val navState = state.navigationState
return when (navigationAction) {
is NavigationAction.NavigateTo -> {
navState.copy(backStack = navState.backStack + navigationAction.screen)
}
is NavigationAction.PopBackTo -> {
if (navState.backStack.lastOrNull() == navigationAction.screen) return navState
val screen = navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen()
val index = navState.backStack.lastIndexOf(screen) + 1
state.navigationState.copy(backStack = navState.backStack.subList(0, index))
}
is NavigationAction.ActivityCreated -> navState.copy(activity = navigationAction.activity)
is NavigationAction.ActivityDestroyed -> {
when {
// Destroy the activity if it invoked for the same activity. Prevents overwriting to null if there is a
// new scan from the background [REDACTED_TASK_KEY]
navState.activity?.get() == navigationAction.activity.get() -> navState.copy(activity = null)
else -> navState
}
}
else -> navState
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.tap.common.settings
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import com.tangem.core.navigation.settings.SettingsManager
internal class IntentSettingsManager(val context: Context) : SettingsManager {
override fun openSettings() {
val openSettingsIntent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", context.packageName, null),
)
context.startActivity(openSettingsIntent)
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.tap.common.share
import android.content.Intent
import com.tangem.core.navigation.share.ShareManager
import com.tangem.tap.foregroundActivityObserver
import com.tangem.tap.withForegroundActivity
internal class IntentShareManager : ShareManager {
override fun shareText(text: String) {
foregroundActivityObserver.withForegroundActivity { activity ->
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, text)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
activity.startActivity(shareIntent)
}
}
}

View file

@ -5,7 +5,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.dispatchOpenUrl
@ -44,7 +43,7 @@ class RussianCardholdersWarningBottomSheetDialog(
dismiss() dismiss()
} }
binding?.btnNo?.setOnClickListener { binding?.btnNo?.setOnClickListener {
store.dispatch(NavigationAction.OpenUrl(INSTRUCTION_URL)) store.dispatchOpenUrl(INSTRUCTION_URL)
dismiss() dismiss()
} }
} }

View file

@ -1,37 +1,76 @@
package com.tangem.tap.common.ui package com.tangem.tap.common.ui
import android.content.Context import android.content.Context
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder import androidx.core.view.isVisible
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.StateDialog import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.analytics.events.ScanFailsDialogAnalytics
import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.feedback.ScanFailsEmail import com.tangem.tap.common.feedback.ScanFailsEmail
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.home.LocaleRegionProvider
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.wallet.R import com.tangem.wallet.R
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
object ScanFailsDialog { internal object ScanFailsDialog {
fun create(context: Context, source: StateDialog.ScanFailsSource): AlertDialog {
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply { private const val HOW_TO_SCAN_RU_LINK = "https://tangem.com/ru/blog/post/scan-tangem-card/"
setTitle(context.getString(R.string.common_warning)) private const val HOW_TO_SCAN_LINK = "https://tangem.com/en/blog/post/scan-tangem-card/"
setMessage(R.string.alert_troubleshooting_scan_card_title)
setPositiveButton(R.string.alert_button_request_support) { _, _ -> fun create(context: Context, source: StateDialog.ScanFailsSource, onTryAgain: (() -> Unit)? = null): AlertDialog {
val sourceAnalytics = when (source) { return AlertDialog.Builder(context, R.style.CustomMaterialDialog).apply {
StateDialog.ScanFailsSource.MAIN -> AnalyticsParam.ScreensSources.Main val customView = View.inflate(context, R.layout.dialog_scan_fails, null)
StateDialog.ScanFailsSource.SIGN_IN -> AnalyticsParam.ScreensSources.SignIn val sourceAnalytics = when (source) {
StateDialog.ScanFailsSource.SETTINGS -> AnalyticsParam.ScreensSources.Settings StateDialog.ScanFailsSource.MAIN -> AnalyticsParam.ScreensSources.Main
StateDialog.ScanFailsSource.INTRO -> AnalyticsParam.ScreensSources.Intro StateDialog.ScanFailsSource.SIGN_IN -> AnalyticsParam.ScreensSources.SignIn
} StateDialog.ScanFailsSource.SETTINGS -> AnalyticsParam.ScreensSources.Settings
Analytics.send(Basic.ButtonSupport(sourceAnalytics)) StateDialog.ScanFailsSource.INTRO -> AnalyticsParam.ScreensSources.Intro
store.dispatch(GlobalAction.SendEmail(ScanFailsEmail()))
} }
setNeutralButton(R.string.common_cancel) { _, _ -> } val tryAgainBtn: TextView? = customView.findViewById(R.id.try_again_button)
if (onTryAgain != null) {
tryAgainBtn?.isVisible = true
tryAgainBtn?.setOnClickListener {
store.dispatchDialogHide()
Analytics.send(
ScanFailsDialogAnalytics(
button = ScanFailsDialogAnalytics.Buttons.TRY_AGAIN,
source = sourceAnalytics,
),
)
onTryAgain()
}
} else {
tryAgainBtn?.isVisible = false
}
customView.findViewById<TextView>(R.id.how_to_scan_button)?.setOnClickListener {
Analytics.send(
ScanFailsDialogAnalytics(
button = ScanFailsDialogAnalytics.Buttons.HOW_TO_SCAN,
source = sourceAnalytics,
),
)
val locale = LocaleRegionProvider().getRegion()
val link = if (locale.lowercase() == RUSSIA_COUNTRY_CODE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK
store.dispatchOpenUrl(link)
}
customView.findViewById<TextView>(R.id.request_support_button)?.setOnClickListener {
Analytics.send(Basic.ButtonSupport(sourceAnalytics))
store.dispatch(GlobalAction.SendEmail(feedbackData = ScanFailsEmail(), scanResponse = null))
}
customView.findViewById<TextView>(R.id.cancel_button)?.setOnClickListener {
store.dispatchDialogHide()
}
setView(customView)
setOnDismissListener { store.dispatchDialogHide() } setOnDismissListener { store.dispatchDialogHide() }
}.create() }.create()
} }

View file

@ -1,4 +1,4 @@
package com.tangem.tap.common package com.tangem.tap.common.url
import android.content.Context import android.content.Context
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
@ -8,12 +8,22 @@ import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_DARK import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_DARK
import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.common.extensions.getColorCompat import com.tangem.tap.common.extensions.getColorCompat
import com.tangem.tap.foregroundActivityObserver
import com.tangem.tap.withForegroundActivity
import com.tangem.wallet.R import com.tangem.wallet.R
class CustomTabsManager { internal class CustomTabsUrlOpener : UrlOpener {
fun openUrl(url: String, context: Context) {
override fun openUrl(url: String) {
foregroundActivityObserver.withForegroundActivity {
openUrl(url, context = it)
}
}
private fun openUrl(url: String, context: Context) {
if (url.isEmpty()) return if (url.isEmpty()) return
val customTabsIntent = CustomTabsIntent.Builder() val customTabsIntent = CustomTabsIntent.Builder()
.setDefaultColorSchemeParams( .setDefaultColorSchemeParams(

View file

@ -4,6 +4,7 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
// FIXME: Workaround, remove it once the normal UserWalletsStore has been implemented // FIXME: Workaround, remove it once the normal UserWalletsStore has been implemented
@ -15,6 +16,9 @@ internal class RuntimeUserWalletsStore(
override val selectedUserWalletOrNull: UserWallet? override val selectedUserWalletOrNull: UserWallet?
get() = userWalletsListManager.selectedUserWalletSync get() = userWalletsListManager.selectedUserWalletSync
override val userWallets: Flow<List<UserWallet>>
get() = userWalletsListManager.userWallets
override suspend fun getSyncOrNull(key: UserWalletId): UserWallet? { override suspend fun getSyncOrNull(key: UserWalletId): UserWallet? {
return userWalletsListManager return userWalletsListManager
.userWallets .userWallets

View file

@ -6,10 +6,8 @@ import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.feature.tester.ActivityClassWrapper
import com.tangem.tap.MainActivity
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.network.exchangeServices.DefaultRampManager import com.tangem.tap.network.exchangeServices.DefaultRampManager
import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.AppStateHolder
import dagger.Module import dagger.Module
@ -67,10 +65,4 @@ internal object ActivityModule {
): GetPolkadotCheckHasImmortalUseCase { ): GetPolkadotCheckHasImmortalUseCase {
return GetPolkadotCheckHasImmortalUseCase(polkadotAccountHealthCheckRepository) return GetPolkadotCheckHasImmortalUseCase(polkadotAccountHealthCheckRepository)
} }
@Provides
@Singleton
fun provideActivityClassWrapper(): ActivityClassWrapper {
return ActivityClassWrapper(MainActivity::class.java)
}
} }

View file

@ -1,6 +1,5 @@
package com.tangem.tap.di package com.tangem.tap.di
import com.tangem.core.navigation.ReduxNavController
import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.AppStateHolder
import dagger.Binds import dagger.Binds
@ -13,10 +12,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
internal interface AppStateHolderModule { internal interface AppStateHolderModule {
@Binds
@Singleton
fun bindsNavigationStateHolder(appStateHolder: AppStateHolder): ReduxNavController
@Binds @Binds
@Singleton @Singleton
fun bindsReduxStateHolder(appStateHolder: AppStateHolder): ReduxStateHolder fun bindsReduxStateHolder(appStateHolder: AppStateHolder): ReduxStateHolder

View file

@ -7,8 +7,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.DefaultAppComponentContext import com.tangem.core.decompose.context.DefaultAppComponentContext
import com.tangem.core.decompose.di.DecomposeComponent import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.di.RootAppComponentContext import com.tangem.core.decompose.di.RootAppComponentContext
import com.tangem.core.decompose.ui.UiMessage import com.tangem.core.ui.UiDependencies
import com.tangem.core.decompose.ui.UiMessageHandler
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -16,7 +15,6 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped import dagger.hilt.android.scopes.ActivityScoped
import timber.log.Timber
@Module @Module
@InstallIn(ActivityComponent::class) @InstallIn(ActivityComponent::class)
@ -29,17 +27,11 @@ internal object RootAppComponentContextModule {
@ActivityContext context: Context, @ActivityContext context: Context,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
componentBuilder: DecomposeComponent.Builder, componentBuilder: DecomposeComponent.Builder,
uiDependencies: UiDependencies,
): AppComponentContext { ): AppComponentContext {
// TODO: Implement message handler
val dummyMessageHandler = object : UiMessageHandler {
override fun handleMessage(message: UiMessage) {
Timber.w("Unable to handle message: $message")
}
}
return DefaultAppComponentContext( return DefaultAppComponentContext(
componentContext = (context as AppCompatActivity).defaultComponentContext(), componentContext = (context as AppCompatActivity).defaultComponentContext(),
messageHandler = dummyMessageHandler, messageHandler = uiDependencies.eventMessageHandler,
dispatchers = dispatchers, dispatchers = dispatchers,
hiltComponentBuilder = componentBuilder, hiltComponentBuilder = componentBuilder,
) )

View file

@ -1,7 +1,9 @@
package com.tangem.tap.di package com.tangem.tap.di
import androidx.compose.material3.SnackbarHostState
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.message.EventMessageHandler
import com.tangem.core.ui.theme.AppThemeModeHolder import com.tangem.core.ui.theme.AppThemeModeHolder
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -19,6 +21,8 @@ internal object UiDependenciesModule {
return object : UiDependencies { return object : UiDependencies {
override val hapticManager = hapticManager override val hapticManager = hapticManager
override val appThemeModeHolder = appThemeModeHolder override val appThemeModeHolder = appThemeModeHolder
override val globalSnackbarHostState: SnackbarHostState = SnackbarHostState()
override val eventMessageHandler: EventMessageHandler = EventMessageHandler()
} }
} }
} }

View file

@ -0,0 +1,44 @@
package com.tangem.tap.di
import android.content.Context
import com.tangem.core.navigation.finisher.AppFinisher
import com.tangem.core.navigation.settings.SettingsManager
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.feedback.FeedbackManager
import com.tangem.tap.common.feedback.ProxyFeedbackManager
import com.tangem.tap.common.finisher.AndroidAppFinisher
import com.tangem.tap.common.settings.IntentSettingsManager
import com.tangem.tap.common.share.IntentShareManager
import com.tangem.tap.common.url.CustomTabsUrlOpener
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object UtilsModule {
@Provides
@Singleton
fun provideShareManager(): ShareManager = IntentShareManager()
@Provides
@Singleton
fun provideUrlOpener(): UrlOpener = CustomTabsUrlOpener()
@Provides
@Singleton
fun provideFeedbackManager(): FeedbackManager = ProxyFeedbackManager()
@Provides
@Singleton
fun provideAppFinisher(@ApplicationContext context: Context): AppFinisher = AndroidAppFinisher(context)
@Provides
@Singleton
fun provideSettingsManager(@ApplicationContext context: Context): SettingsManager = IntentSettingsManager(context)
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.di.domain package com.tangem.tap.di.domain
import android.content.Context import android.content.Context
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.GetFeedbackEmailUseCase import com.tangem.domain.feedback.GetFeedbackEmailUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.repository.FeedbackRepository import com.tangem.domain.feedback.repository.FeedbackRepository
@ -15,6 +16,12 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
internal object FeedbackDomainModule { internal object FeedbackDomainModule {
@Provides
@Singleton
fun provideGetCardInfoUseCase(feedbackRepository: FeedbackRepository): GetCardInfoUseCase {
return GetCardInfoUseCase(feedbackRepository = feedbackRepository)
}
@Provides @Provides
@Singleton @Singleton
fun provideGetFeedbackEmailUseCase( fun provideGetFeedbackEmailUseCase(

View file

@ -7,6 +7,7 @@ import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.settings.* import com.tangem.domain.settings.*
import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.PermissionRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.sdk.TangemSdkManager
@ -17,6 +18,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton import javax.inject.Singleton
@Suppress("TooManyFunctions")
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
internal object SettingsDomainModule { internal object SettingsDomainModule {
@ -164,4 +166,52 @@ internal object SettingsDomainModule {
): IncrementAppLaunchCounterUseCase { ): IncrementAppLaunchCounterUseCase {
return IncrementAppLaunchCounterUseCase(settingsRepository = settingsRepository) return IncrementAppLaunchCounterUseCase(settingsRepository = settingsRepository)
} }
// region PushPermissionRepository
@Provides
@Singleton
fun provideShouldInitiallyAskPermissionUseCase(
permissionRepository: PermissionRepository,
): ShouldInitiallyAskPermissionUseCase {
return ShouldInitiallyAskPermissionUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideIsFirstTimeAskingPermissionUseCase(
permissionRepository: PermissionRepository,
): IsFirstTimeAskingPermissionUseCase {
return IsFirstTimeAskingPermissionUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideSetFirstTimeAskingPushPermissionUseCase(
permissionRepository: PermissionRepository,
): SetFirstTimeAskingPermissionUseCase {
return SetFirstTimeAskingPermissionUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideDelayPermissionRequestUseCase(
permissionRepository: PermissionRepository,
): DelayPermissionRequestUseCase {
return DelayPermissionRequestUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideShouldAskPermissionUseCase(permissionRepository: PermissionRepository): ShouldAskPermissionUseCase {
return ShouldAskPermissionUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideNeverRequestPermissionUseCase(
permissionRepository: PermissionRepository,
): NeverRequestPermissionUseCase {
return NeverRequestPermissionUseCase(repository = permissionRepository)
}
// endregion
} }

View file

@ -1,8 +1,6 @@
package com.tangem.tap.di.domain package com.tangem.tap.di.domain
import com.tangem.domain.settings.* import com.tangem.domain.staking.*
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.staking.repositories.StakingRepository
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -14,6 +12,14 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
internal object StakingDomainModule { internal object StakingDomainModule {
@Provides
@Singleton
fun provideGetStakingAvailabilityUseCase(stakingRepository: StakingRepository): GetStakingAvailabilityUseCase {
return GetStakingAvailabilityUseCase(
stakingRepository = stakingRepository,
)
}
@Provides @Provides
@Singleton @Singleton
fun provideGetStakingEntryInfoUseCase(stakingRepository: StakingRepository): GetStakingEntryInfoUseCase { fun provideGetStakingEntryInfoUseCase(stakingRepository: StakingRepository): GetStakingEntryInfoUseCase {
@ -24,9 +30,35 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideGetStakingAvailabilityUseCase(stakingRepository: StakingRepository): GetStakingAvailabilityUseCase { fun provideGetYieldUseCase(stakingRepository: StakingRepository): GetYieldUseCase {
return GetStakingAvailabilityUseCase( return GetYieldUseCase(
stakingRepository = stakingRepository, stakingRepository = stakingRepository,
) )
} }
@Provides
@Singleton
fun provideGetStakingTokensUseCase(stakingRepository: StakingRepository): FetchStakingTokensUseCase {
return FetchStakingTokensUseCase(
stakingRepository = stakingRepository,
)
}
@Provides
@Singleton
fun provideFetchStakingYieldBalanceUseCase(stakingRepository: StakingRepository): FetchStakingYieldBalanceUseCase {
return FetchStakingYieldBalanceUseCase(stakingRepository = stakingRepository)
}
@Provides
@Singleton
fun provideGetStakingYieldBalanceUseCase(stakingRepository: StakingRepository): GetStakingYieldBalanceUseCase {
return GetStakingYieldBalanceUseCase(stakingRepository = stakingRepository)
}
@Provides
@Singleton
fun provideCreateEnterActionUseCase(stakingRepository: StakingRepository): InitializeStakingProcessUseCase {
return InitializeStakingProcessUseCase(stakingRepository)
}
} }

View file

@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.* import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.repository.* import com.tangem.domain.tokens.repository.*
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
@ -54,8 +55,14 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
): GetTokenListUseCase { ): GetTokenListUseCase {
return GetTokenListUseCase(currenciesRepository, quotesRepository, networksRepository) return GetTokenListUseCase(
currenciesRepository,
quotesRepository,
networksRepository,
stakingRepository,
)
} }
@Provides @Provides
@ -64,8 +71,9 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
): GetCardTokensListUseCase { ): GetCardTokensListUseCase {
return GetCardTokensListUseCase(currenciesRepository, quotesRepository, networksRepository) return GetCardTokensListUseCase(currenciesRepository, quotesRepository, networksRepository, stakingRepository)
} }
@Provides @Provides
@ -83,9 +91,16 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetCurrencyStatusUpdatesUseCase { ): GetCurrencyStatusUpdatesUseCase {
return GetCurrencyStatusUpdatesUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers) return GetCurrencyStatusUpdatesUseCase(
currenciesRepository,
quotesRepository,
networksRepository,
stakingRepository,
dispatchers,
)
} }
@Provides @Provides
@ -100,6 +115,7 @@ internal object TokensDomainModule {
currencyChecksRepository: CurrencyChecksRepository, currencyChecksRepository: CurrencyChecksRepository,
showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
promoRepository: PromoRepository, promoRepository: PromoRepository,
stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetCurrencyWarningsUseCase { ): GetCurrencyWarningsUseCase {
return GetCurrencyWarningsUseCase( return GetCurrencyWarningsUseCase(
@ -112,6 +128,7 @@ internal object TokensDomainModule {
swapRepository = swapRepository, swapRepository = swapRepository,
showSwapPromoTokenUseCase = showSwapPromoTokenUseCase, showSwapPromoTokenUseCase = showSwapPromoTokenUseCase,
promoRepository = promoRepository, promoRepository = promoRepository,
stakingRepository = stakingRepository,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }
@ -122,12 +139,14 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetPrimaryCurrencyStatusUpdatesUseCase { ): GetPrimaryCurrencyStatusUpdatesUseCase {
return GetPrimaryCurrencyStatusUpdatesUseCase( return GetPrimaryCurrencyStatusUpdatesUseCase(
currenciesRepository, currenciesRepository,
quotesRepository, quotesRepository,
networksRepository, networksRepository,
stakingRepository,
dispatchers, dispatchers,
) )
} }
@ -148,8 +167,9 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
): FetchCardTokenListUseCase { ): FetchCardTokenListUseCase {
return FetchCardTokenListUseCase(currenciesRepository, networksRepository, quotesRepository) return FetchCardTokenListUseCase(currenciesRepository, networksRepository, quotesRepository, stakingRepository)
} }
@Provides @Provides
@ -190,6 +210,7 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetCryptoCurrencyActionsUseCase { ): GetCryptoCurrencyActionsUseCase {
return GetCryptoCurrencyActionsUseCase( return GetCryptoCurrencyActionsUseCase(
@ -199,6 +220,7 @@ internal object TokensDomainModule {
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository, quotesRepository = quotesRepository,
networksRepository = networksRepository, networksRepository = networksRepository,
stakingRepository = stakingRepository,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }
@ -209,12 +231,14 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetNetworkCoinStatusUseCase { ): GetNetworkCoinStatusUseCase {
return GetNetworkCoinStatusUseCase( return GetNetworkCoinStatusUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository, quotesRepository = quotesRepository,
networksRepository = networksRepository, networksRepository = networksRepository,
stakingRepository = stakingRepository,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }
@ -225,12 +249,14 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetFeePaidCryptoCurrencyStatusSyncUseCase { ): GetFeePaidCryptoCurrencyStatusSyncUseCase {
return GetFeePaidCryptoCurrencyStatusSyncUseCase( return GetFeePaidCryptoCurrencyStatusSyncUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository, quotesRepository = quotesRepository,
networksRepository = networksRepository, networksRepository = networksRepository,
stakingRepository = stakingRepository,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }
@ -363,11 +389,13 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
stakingRepository: StakingRepository,
): GetWalletTotalBalanceUseCase { ): GetWalletTotalBalanceUseCase {
return GetWalletTotalBalanceUseCase( return GetWalletTotalBalanceUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository, quotesRepository = quotesRepository,
networksRepository = networksRepository, networksRepository = networksRepository,
stakingRepository = stakingRepository,
) )
} }

View file

@ -64,6 +64,20 @@ internal object TransactionDomainModule {
return CreateTransactionUseCase(transactionRepository) return CreateTransactionUseCase(transactionRepository)
} }
@Provides
@Singleton
fun provideCreateTransactionExtrasUseCase(
transactionRepository: TransactionRepository,
): CreateTransactionDataExtrasUseCase {
return CreateTransactionDataExtrasUseCase(transactionRepository)
}
@Provides
@Singleton
fun provideEstimateFeeUseCase(walletManagersFacade: WalletManagersFacade): EstimateFeeUseCase {
return EstimateFeeUseCase(walletManagersFacade)
}
@Provides @Provides
@Singleton @Singleton
fun provideIsFeeApproximateUseCase(feeRepository: FeeRepository): IsFeeApproximateUseCase { fun provideIsFeeApproximateUseCase(feeRepository: FeeRepository): IsFeeApproximateUseCase {

View file

@ -0,0 +1,26 @@
package com.tangem.tap.di.routing
import com.tangem.common.routing.AppRouter
import com.tangem.tap.routing.ProxyAppRouter
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.tap.routing.configurator.MutableAppRouterConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object AppRouterModule {
@Provides
@Singleton
fun provideAppRouter(config: AppRouterConfig, dispatchers: CoroutineDispatcherProvider): AppRouter =
ProxyAppRouter(config, dispatchers)
@Provides
@Singleton
fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig()
}

View file

@ -0,0 +1,18 @@
package com.tangem.tap.di.routing
import com.tangem.tap.routing.RoutingComponent
import com.tangem.tap.routing.impl.DefaultRoutingComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.scopes.ActivityScoped
@Module
@InstallIn(ActivityComponent::class)
internal interface RoutingComponentModule {
@Binds
@ActivityScoped
fun bindRoutingComponentFactory(factory: DefaultRoutingComponent.Factory): RoutingComponent.Factory
}

View file

@ -1,13 +1,17 @@
package com.tangem.tap.domain.card package com.tangem.tap.domain.card
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.TangemSdkError
import com.tangem.common.doOnFailure import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.toMapKey
import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWallet
@ -45,7 +49,10 @@ internal class DefaultDerivationsRepository(
tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations) tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)
.doOnSuccess { response -> .doOnSuccess { response ->
updatePublicKeys(userWalletId = userWalletId, keys = response.entries).fold( updatePublicKeys(userWalletId = userWalletId, keys = response.entries).fold(
onSuccess = { return }, onSuccess = {
validateDerivations(userWallet.scanResponse, derivations)
return
},
onFailure = { throw it }, onFailure = { throw it },
) )
} }
@ -76,6 +83,22 @@ internal class DefaultDerivationsRepository(
} }
} }
/**
* It throws an exception if any of the provided derivations are invalid
* Validation for NonHardened moved to application layer, to avoid fails when derive multiple paths
* It needs to be called after success [derivePublicKeys] or in same flows
*/
private fun validateDerivations(scanResponse: ScanResponse, derivations: Derivations) {
derivations.entries.forEach { derivationForKey ->
val wallet = scanResponse.card.wallets.firstOrNull { it.publicKey.toMapKey() == derivationForKey.key }
if (wallet == null) return@forEach
val hasHardenedNodes = derivationForKey.value.any { path -> path.nodes.any { node -> !node.isHardened } }
if (wallet.curve == EllipticCurve.Ed25519Slip0010 && hasHardenedNodes) {
throw TangemSdkError.NonHardenedDerivationNotSupported()
}
}
}
private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): Result<Unit> { private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): Result<Unit> {
return runCatching(dispatchers.io) { return runCatching(dispatchers.io) {
userWalletsStore.update( userWalletsStore.update(

View file

@ -5,7 +5,7 @@ import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.Wallet
data class PendingTransaction( data class PendingTransaction(
val transactionData: TransactionData, val transactionData: TransactionData.Uncompiled,
val type: PendingTransactionType, val type: PendingTransactionType,
) { ) {
val address: String? = when (type) { val address: String? = when (type) {
@ -21,7 +21,7 @@ data class PendingTransaction(
enum class PendingTransactionType { Incoming, Outgoing, Unknown } enum class PendingTransactionType { Incoming, Outgoing, Unknown }
fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransaction? { fun TransactionData.Uncompiled.toPendingTransaction(walletAddress: String): PendingTransaction? {
if (this.status == TransactionStatus.Confirmed) return null if (this.status == TransactionStatus.Confirmed) return null
val type: PendingTransactionType = when { val type: PendingTransactionType = when {
@ -32,7 +32,7 @@ fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransact
return PendingTransaction(this, type) return PendingTransaction(this, type)
} }
fun List<TransactionData>.toPendingTransactions(walletAddress: String): List<PendingTransaction> { fun List<TransactionData.Uncompiled>.toPendingTransactions(walletAddress: String): List<PendingTransaction> {
return this.mapNotNull { it.toPendingTransaction(walletAddress) } return this.mapNotNull { it.toPendingTransaction(walletAddress) }
} }

View file

@ -5,12 +5,11 @@ import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.common.doOnFailure import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.canSkipBackup import com.tangem.domain.common.TapWorkarounds.canSkipBackup
import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.common.util.twinsIsTwinned
@ -21,6 +20,7 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.createDisclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
@ -118,7 +118,7 @@ internal object LegacyScanProcessor {
disclaimerWillShow() disclaimerWillShow()
store.dispatchWithMain( store.dispatchWithMain(
DisclaimerAction.Show( DisclaimerAction.Show(
fromScreen = AppScreen.Home, from = DisclaimerSource.Home,
callback = DisclaimerCallback( callback = DisclaimerCallback(
onAccept = { onAccept = {
scope.launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {
@ -170,7 +170,7 @@ internal object LegacyScanProcessor {
if (scanResponse.twinsIsTwinned() && !wasTwinsOnboardingShown) { if (scanResponse.twinsIsTwinned() && !wasTwinsOnboardingShown) {
onWalletNotCreated() onWalletNotCreated()
store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse)))
navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } navigateTo(AppRoute.OnboardingTwins) { onProgressStateChange(it) }
} else { } else {
delay(DELAY_SDK_DIALOG_CLOSE) delay(DELAY_SDK_DIALOG_CLOSE)
onSuccess(scanResponse) onSuccess(scanResponse)
@ -178,9 +178,9 @@ internal object LegacyScanProcessor {
} }
} }
private suspend inline fun navigateTo(screen: AppScreen, onProgressStateChange: (showProgress: Boolean) -> Unit) { private suspend inline fun navigateTo(route: AppRoute, onProgressStateChange: (showProgress: Boolean) -> Unit) {
delay(DELAY_SDK_DIALOG_CLOSE) delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatchOnMain(NavigationAction.NavigateTo(screen)) store.dispatchNavigationAction { push(route) }
onProgressStateChange(false) onProgressStateChange(false)
} }
} }

View file

@ -3,16 +3,15 @@ package com.tangem.tap.domain.scanCard
import arrow.fx.coroutines.resourceScope import arrow.fx.coroutines.resourceScope
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError import com.tangem.common.core.TangemError
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.StateDialog
import com.tangem.domain.card.ScanCardException import com.tangem.domain.card.ScanCardException
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.inject
import com.tangem.tap.domain.scanCard.chains.* import com.tangem.tap.domain.scanCard.chains.*
import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter
@ -60,7 +59,11 @@ internal object UseCaseScanProcessor {
), ),
) )
add(AnalyticsChain(Basic.CardWasScanned(analyticsSource))) add(AnalyticsChain(Basic.CardWasScanned(analyticsSource)))
add(DisclaimerChain(store, disclaimerWillShow)) val pushNotificationsToggles =
store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles)
if (pushNotificationsToggles.isPushNotificationsEnabled) {
add(DisclaimerChain(store, disclaimerWillShow))
}
add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager)) add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager))
} }
@ -132,8 +135,8 @@ internal object UseCaseScanProcessor {
action() action()
} }
private suspend inline fun navigateTo(screen: AppScreen) { private suspend inline fun navigateTo(route: AppRoute) {
delay(DELAY_SDK_DIALOG_CLOSE) delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatchOnMain(NavigationAction.NavigateTo(screen)) store.dispatchNavigationAction { push(route) }
} }
} }

View file

@ -2,8 +2,8 @@ package com.tangem.tap.domain.scanCard.chains
import arrow.core.left import arrow.core.left
import arrow.core.right import arrow.core.right
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.domain.card.ScanCardException import com.tangem.domain.card.ScanCardException
import com.tangem.domain.common.TapWorkarounds.canSkipBackup import com.tangem.domain.common.TapWorkarounds.canSkipBackup
import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.common.util.twinsIsTwinned
@ -55,8 +55,8 @@ class CheckForOnboardingChain(
canSkipBackup = previousChainResult.card.canSkipBackup, canSkipBackup = previousChainResult.card.canSkipBackup,
), ),
) )
val appScreen = OnboardingHelper.whereToNavigate(previousChainResult) val route = OnboardingHelper.whereToNavigate(previousChainResult)
ScanChainException.OnboardingNeeded(appScreen).left() ScanChainException.OnboardingNeeded(route).left()
} }
else -> { else -> {
Analytics.setContext(previousChainResult) Analytics.setContext(previousChainResult)
@ -69,7 +69,7 @@ class CheckForOnboardingChain(
store.dispatchOnMain( store.dispatchOnMain(
TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(previousChainResult)), TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(previousChainResult)),
) )
ScanChainException.OnboardingNeeded(AppScreen.OnboardingTwins).left() ScanChainException.OnboardingNeeded(AppRoute.OnboardingTwins).left()
} else { } else {
delay(DELAY_SDK_DIALOG_CLOSE) delay(DELAY_SDK_DIALOG_CLOSE)
previousChainResult.right() previousChainResult.right()

View file

@ -2,7 +2,6 @@ package com.tangem.tap.domain.scanCard.chains
import arrow.core.left import arrow.core.left
import arrow.core.right import arrow.core.right
import com.tangem.core.navigation.AppScreen
import com.tangem.domain.card.ScanCardException import com.tangem.domain.card.ScanCardException
import com.tangem.domain.core.chain.Chain import com.tangem.domain.core.chain.Chain
import com.tangem.domain.core.chain.ResultChain import com.tangem.domain.core.chain.ResultChain
@ -13,6 +12,7 @@ import com.tangem.tap.features.disclaimer.Disclaimer
import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.createDisclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import org.rekotlin.Store import org.rekotlin.Store
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -50,7 +50,7 @@ internal class DisclaimerChain(
return suspendCancellableCoroutine { continuation -> return suspendCancellableCoroutine { continuation ->
store.dispatchOnMain( store.dispatchOnMain(
DisclaimerAction.Show( DisclaimerAction.Show(
fromScreen = AppScreen.Home, from = DisclaimerSource.Home,
callback = DisclaimerCallback( callback = DisclaimerCallback(
onAccept = { onAccept = {
if (continuation.isActive) { if (continuation.isActive) {

View file

@ -1,6 +1,6 @@
package com.tangem.tap.domain.scanCard.chains package com.tangem.tap.domain.scanCard.chains
import com.tangem.core.navigation.AppScreen import com.tangem.common.routing.AppRoute
import com.tangem.domain.card.ScanCardException import com.tangem.domain.card.ScanCardException
sealed class ScanChainException : ScanCardException.ChainException() { sealed class ScanChainException : ScanCardException.ChainException() {
@ -19,5 +19,5 @@ sealed class ScanChainException : ScanCardException.ChainException() {
* *
* @param onboardingRoute route where to navigate * @param onboardingRoute route where to navigate
* */ * */
data class OnboardingNeeded(val onboardingRoute: AppScreen) : ScanChainException() data class OnboardingNeeded(val onboardingRoute: AppRoute) : ScanChainException()
} }

View file

@ -91,9 +91,8 @@ class WalletConnectSdkHelper {
val destinationAddress = requireNotNull(transaction.to) { "Destination address is null" } val destinationAddress = requireNotNull(transaction.to) { "Destination address is null" }
val transactionData = TransactionData( val transactionData = TransactionData.Uncompiled(
amount = Amount(value, wallet.blockchain), amount = Amount(value, wallet.blockchain),
// TODO refactoring
fee = Fee.Common(Amount(fee, wallet.blockchain)), fee = Fee.Common(Amount(fee, wallet.blockchain)),
sourceAddress = transaction.from, sourceAddress = transaction.from,
destinationAddress = destinationAddress, destinationAddress = destinationAddress,

View file

@ -14,7 +14,8 @@ import androidx.lifecycle.Lifecycle
import androidx.transition.TransitionInflater import androidx.transition.TransitionInflater
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.tangem.common.extensions.VoidCallback import com.tangem.common.extensions.VoidCallback
import com.tangem.core.navigation.NavigationAction import com.tangem.common.routing.AppRouter
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.wallet.R import com.tangem.wallet.R
@ -36,7 +37,7 @@ abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackP
} }
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.customtoken.impl.presentation package com.tangem.tap.features.customtoken.impl.presentation
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -8,7 +9,6 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenScreen import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenScreen
@ -32,15 +32,12 @@ internal class AddCustomTokenFragment : ComposeFragment() {
val viewModel = hiltViewModel<AddCustomTokenViewModel>().apply { val viewModel = hiltViewModel<AddCustomTokenViewModel>().apply {
LocalLifecycleOwner.current.lifecycle.addObserver(this) LocalLifecycleOwner.current.lifecycle.addObserver(this)
} }
val statusBarColor = TangemTheme.colors.background.secondary
SystemBarsEffect {
setSystemBarsColor(color = statusBarColor)
}
val state by viewModel.uiState.collectAsStateWithLifecycle() val state by viewModel.uiState.collectAsStateWithLifecycle()
AddCustomTokenScreen( AddCustomTokenScreen(
modifier = Modifier.systemBarsPadding(), modifier = Modifier
.background(TangemTheme.colors.background.primary)
.systemBarsPadding(),
stateHolder = state, stateHolder = state,
) )
} }

View file

@ -1,9 +1,11 @@
package com.tangem.tap.features.customtoken.impl.presentation.routers package com.tangem.tap.features.customtoken.impl.presentation.routers
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.core.navigation.AppScreen import com.tangem.common.routing.AppRoute
import com.tangem.core.navigation.NavigationAction import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.utils.popTo
import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.wallet.R import com.tangem.wallet.R
@ -12,11 +14,11 @@ import com.tangem.wallet.R
internal class DefaultCustomTokenRouter : CustomTokenRouter { internal class DefaultCustomTokenRouter : CustomTokenRouter {
override fun popBackStack() { override fun popBackStack() {
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
} }
override fun openWalletScreen() { override fun openWalletScreen() {
store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Wallet)) store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
} }
override fun openUnsupportedNetworkAlert(blockchain: Blockchain) { override fun openUnsupportedNetworkAlert(blockchain: Blockchain) {
@ -32,7 +34,7 @@ internal class DefaultCustomTokenRouter : CustomTokenRouter {
val alert = AppDialog.SimpleOkDialogRes( val alert = AppDialog.SimpleOkDialogRes(
headerId = R.string.common_error, headerId = R.string.common_error,
messageId = R.string.common_unknown_error, messageId = R.string.common_unknown_error,
onOk = { store.dispatch(NavigationAction.PopBackTo()) }, onOk = { store.dispatchNavigationAction(AppRouter::pop) },
) )
store.dispatchDialogShow(alert) store.dispatchDialogShow(alert)
} }

View file

@ -96,6 +96,8 @@ sealed class DetailsAction : Action {
data class ChangeAppCurrency( data class ChangeAppCurrency(
val currency: AppCurrency, val currency: AppCurrency,
) : AppSettings() ) : AppSettings()
data class Prepare(val state: AppSettingsState) : AppSettings()
} }
data class ChangeAppCurrency(val currency: AppCurrency) : DetailsAction() data class ChangeAppCurrency(val currency: AppCurrency) : DetailsAction()

View file

@ -5,9 +5,11 @@ import com.tangem.common.*
import com.tangem.common.core.TangemError import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.common.core.UserCodeRequestPolicy import com.tangem.common.core.UserCodeRequestPolicy
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
@ -19,6 +21,7 @@ import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListError import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.domain.wallets.legacy.asLockable import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.* import com.tangem.tap.common.extensions.*
@ -29,9 +32,6 @@ import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn import com.tangem.utils.coroutines.saveIn
import com.tangem.wallet.R import com.tangem.wallet.R
@ -71,7 +71,8 @@ class DetailsMiddleware {
is DetailsAction.AppSettings -> appSettingsMiddleware.handle(state, action) is DetailsAction.AppSettings -> appSettingsMiddleware.handle(state, action)
is DetailsAction.ReCreateTwinsWallet -> { is DetailsAction.ReCreateTwinsWallet -> {
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
store.dispatchNavigationAction { push(AppRoute.OnboardingTwins) }
} }
is DetailsAction.AccessCodeRecovery -> accessCodeRecoveryMiddleware.handle(state, action) is DetailsAction.AccessCodeRecovery -> accessCodeRecoveryMiddleware.handle(state, action)
is DetailsAction.ScanCard -> scanCard(state) is DetailsAction.ScanCard -> scanCard(state)
@ -91,7 +92,7 @@ class DetailsMiddleware {
store.dispatch(DetailsAction.ReCreateTwinsWallet) store.dispatch(DetailsAction.ReCreateTwinsWallet)
return return
} else { } else {
store.dispatch(NavigationAction.NavigateTo(AppScreen.ResetToFactory)) store.dispatchNavigationAction { push(AppRoute.ResetToFactory) }
} }
} }
is DetailsAction.ResetToFactory.Proceed -> { is DetailsAction.ResetToFactory.Proceed -> {
@ -132,15 +133,15 @@ class DetailsMiddleware {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
if (selectedUserWallet != null) { if (selectedUserWallet != null) {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
store.onUserWalletSelected(selectedUserWallet) store.onUserWalletSelected(selectedUserWallet)
} else { } else {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync } val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
.fold(onSuccess = { true }, onFailure = { false }) .fold(onSuccess = { true }, onFailure = { false })
if (isLocked && userWalletsListManager.hasUserWallets) { if (isLocked && userWalletsListManager.hasUserWallets) {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
} else { } else {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
} }
} }
} }
@ -164,7 +165,7 @@ class DetailsMiddleware {
fun handle(action: DetailsAction.ManageSecurity, detailsState: DetailsState) { fun handle(action: DetailsAction.ManageSecurity, detailsState: DetailsState) {
when (action) { when (action) {
is DetailsAction.ManageSecurity.OpenSecurity -> { is DetailsAction.ManageSecurity.OpenSecurity -> {
store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity)) store.dispatchNavigationAction { push(AppRoute.DetailsSecurity) }
} }
is DetailsAction.ManageSecurity.SaveChanges -> { is DetailsAction.ManageSecurity.SaveChanges -> {
val cardSettingsState = detailsState.cardSettingsState val cardSettingsState = detailsState.cardSettingsState
@ -183,7 +184,7 @@ class DetailsMiddleware {
is CompletionResult.Success -> { is CompletionResult.Success -> {
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue)) Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue))
store.dispatch(GlobalAction.UpdateSecurityOptions(selectedOption)) store.dispatch(GlobalAction.UpdateSecurityOptions(selectedOption))
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success) store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success)
} }
is CompletionResult.Failure -> { is CompletionResult.Failure -> {
@ -243,6 +244,7 @@ class DetailsMiddleware {
is DetailsAction.AppSettings.SwitchPrivacySetting.Success, is DetailsAction.AppSettings.SwitchPrivacySetting.Success,
is DetailsAction.AppSettings.SwitchPrivacySetting.Failure, is DetailsAction.AppSettings.SwitchPrivacySetting.Failure,
is DetailsAction.AppSettings.BiometricsStatusChanged, is DetailsAction.AppSettings.BiometricsStatusChanged,
is DetailsAction.AppSettings.Prepare,
-> Unit -> Unit
} }
} }
@ -272,7 +274,7 @@ class DetailsMiddleware {
private fun enrollBiometrics() { private fun enrollBiometrics() {
Analytics.send(Settings.AppSettings.ButtonEnableBiometricAuthentication) Analytics.send(Settings.AppSettings.ButtonEnableBiometricAuthentication)
store.dispatchOnMain(NavigationAction.OpenBiometricsSettings) activityResultCaller.openSystemBiometrySettings()
} }
private fun changeAppThemeMode(appThemeMode: AppThemeMode) { private fun changeAppThemeMode(appThemeMode: AppThemeMode) {
@ -393,7 +395,7 @@ class DetailsMiddleware {
deleteSavedAccessCodes() deleteSavedAccessCodes()
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false) store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home)) store.dispatchNavigationAction { popTo<AppRoute.Home>() }
return CompletionResult.Success(Unit) return CompletionResult.Success(Unit)
} }
@ -432,7 +434,7 @@ class DetailsMiddleware {
when (action) { when (action) {
is DetailsAction.AccessCodeRecovery.Open -> { is DetailsAction.AccessCodeRecovery.Open -> {
Analytics.send(Settings.CardSettings.AccessCodeRecoveryButton()) Analytics.send(Settings.CardSettings.AccessCodeRecoveryButton())
store.dispatch(NavigationAction.NavigateTo(AppScreen.AccessCodeRecovery)) store.dispatchNavigationAction { push(AppRoute.AccessCodeRecovery) }
} }
is DetailsAction.AccessCodeRecovery.SaveChanges -> { is DetailsAction.AccessCodeRecovery.SaveChanges -> {
scope.launch { scope.launch {
@ -444,7 +446,7 @@ class DetailsMiddleware {
AnalyticsParam.AccessCodeRecoveryStatus.from(action.enabled), AnalyticsParam.AccessCodeRecoveryStatus.from(action.enabled),
), ),
) )
store.dispatchOnMain(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
store.dispatchOnMain( store.dispatchOnMain(
DetailsAction.AccessCodeRecovery.SaveChanges.Success(action.enabled), DetailsAction.AccessCodeRecovery.SaveChanges.Success(action.enabled),
) )
@ -500,7 +502,7 @@ class DetailsMiddleware {
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success) store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
}, },
disclaimerWillShow = { disclaimerWillShow = {
store.dispatchOnMain(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
}, },
onSuccess = { scanResponse -> onSuccess = { scanResponse ->
createUserWallet(scanResponse) createUserWallet(scanResponse)
@ -544,7 +546,7 @@ class DetailsMiddleware {
store.onUserWalletSelected(userWallet) store.onUserWalletSelected(userWallet)
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success) store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
} }
.doOnFailure { error -> .doOnFailure { error ->
if (error is UserWalletsListError.WalletAlreadySaved) { if (error is UserWalletsListError.WalletAlreadySaved) {
@ -552,7 +554,7 @@ class DetailsMiddleware {
store.onUserWalletSelected(userWallet) store.onUserWalletSelected(userWallet)
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success) store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
} else { } else {
Timber.e(error, "Unable to create user wallet") Timber.e(error, "Unable to create user wallet")
handleError(error, prevUseBiometricsForAccessCode) handleError(error, prevUseBiometricsForAccessCode)

View file

@ -1,6 +1,6 @@
package com.tangem.tap.features.details.redux package com.tangem.tap.features.details.redux
import com.tangem.core.navigation.AppScreen import com.tangem.common.routing.AppRoute
import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
@ -66,10 +66,12 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
} }
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppState): DetailsState { private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppState): DetailsState {
val router = store.inject(DaggerGraphState::appRouter)
return DetailsState( return DetailsState(
scanResponse = action.scanResponse, scanResponse = action.scanResponse,
// If the current screen is ResetToFactory, we must save the ResetToFactory's state // If the current screen is ResetToFactory, we must save the ResetToFactory's state
cardSettingsState = if (store.state.navigationState.backStack.lastOrNull() == AppScreen.ResetToFactory) { cardSettingsState = if (router.stack.lastOrNull() is AppRoute.ResetToFactory) {
state.detailsState.cardSettingsState state.detailsState.cardSettingsState
} else { } else {
null null
@ -279,6 +281,9 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
isHidingEnabled = action.hideBalance, isHidingEnabled = action.hideBalance,
), ),
) )
is DetailsAction.AppSettings.Prepare -> state.copy(
appSettingsState = action.state,
)
is DetailsAction.AppSettings.EnrollBiometrics, is DetailsAction.AppSettings.EnrollBiometrics,
is DetailsAction.AppSettings.CheckBiometricsStatus, is DetailsAction.AppSettings.CheckBiometricsStatus,
-> state -> state

View file

@ -1,19 +1,17 @@
package com.tangem.tap.features.details.redux.walletconnect package com.tangem.tap.features.details.redux.walletconnect
import androidx.core.os.bundleOf
import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.navigation.AppScreen import com.tangem.common.routing.AppRoute
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.models.Account import com.tangem.tap.domain.walletconnect2.domain.models.Account
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
@ -62,14 +60,7 @@ class WalletConnectMiddleware {
// TODO check // TODO check
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri)) store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
} else { } else {
store.dispatchOnMain( store.dispatchNavigationAction { push(AppRoute.QrScanning(SourceType.WALLET_CONNECT)) }
NavigationAction.NavigateTo(
screen = AppScreen.QrScanning,
bundle = bundleOf(
QrScanningRouter.SOURCE_KEY to SourceType.WALLET_CONNECT,
),
),
)
} }
} }
is WalletConnectAction.ShowClipboardOrScanQrDialog -> { is WalletConnectAction.ShowClipboardOrScanQrDialog -> {

View file

@ -5,9 +5,9 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.derivation.DerivationStyle import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.core.navigation.StateDialog
import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents

View file

@ -6,8 +6,7 @@ import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@ -22,12 +21,8 @@ internal class AppCurrencySelectorFragment : ComposeFragment() {
@Composable @Composable
override fun ScreenContent(modifier: Modifier) { override fun ScreenContent(modifier: Modifier) {
val systemBarsColor = TangemTheme.colors.background.secondary
SystemBarsEffect {
setSystemBarsColor(systemBarsColor)
}
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
NavigationBar3ButtonsScrim()
AppCurrencySelectorScreen( AppCurrencySelectorScreen(
modifier = modifier, modifier = modifier,
state = uiState, state = uiState,

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -40,7 +41,9 @@ internal fun AppCurrencySelectorScreen(state: AppCurrencySelectorState, modifier
val listState = rememberLazyListState() val listState = rememberLazyListState()
Scaffold( Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = modifier
.imePadding()
.nestedScroll(scrollBehavior.nestedScrollConnection),
containerColor = TangemTheme.colors.background.secondary, containerColor = TangemTheme.colors.background.secondary,
topBar = { topBar = {
TopBar( TopBar(
@ -49,6 +52,7 @@ internal fun AppCurrencySelectorScreen(state: AppCurrencySelectorState, modifier
state = state, state = state,
) )
}, },
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.navigationBars),
content = { paddingValues -> content = { paddingValues ->
val contentModifier = Modifier val contentModifier = Modifier
.padding(paddingValues) .padding(paddingValues)
@ -62,7 +66,7 @@ internal fun AppCurrencySelectorScreen(state: AppCurrencySelectorState, modifier
modifier = contentModifier, modifier = contentModifier,
listState = listState, listState = listState,
currencies = state.items, currencies = state.items,
selectedId = state.selectedId.orEmpty(), selectedId = state.selectedId,
onCurrencyClick = state.onCurrencyClick, onCurrencyClick = state.onCurrencyClick,
) )
} }
@ -175,6 +179,7 @@ private fun SearchBar(onInputChange: (String) -> Unit, modifier: Modifier = Modi
@Composable @Composable
private fun LoadingList(modifier: Modifier = Modifier) { private fun LoadingList(modifier: Modifier = Modifier) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
Column(modifier = modifier) { Column(modifier = modifier) {
repeat(times = 10) { repeat(times = 10) {
Row( Row(
@ -199,6 +204,7 @@ private fun LoadingList(modifier: Modifier = Modifier) {
) )
} }
} }
Spacer(Modifier.height(bottomBarHeight))
} }
} }
@ -210,9 +216,11 @@ private fun CurrenciesList(
onCurrencyClick: (Currency) -> Unit, onCurrencyClick: (Currency) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
LazyColumn( LazyColumn(
modifier = modifier, modifier = modifier,
state = listState, state = listState,
contentPadding = PaddingValues(bottom = bottomBarHeight),
) { ) {
items( items(
items = currencies, items = currencies,

View file

@ -2,8 +2,9 @@ package com.tangem.tap.features.details.ui.appcurrency
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.navigation.ReduxNavController
import com.tangem.domain.appcurrency.GetAvailableCurrenciesUseCase import com.tangem.domain.appcurrency.GetAvailableCurrenciesUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.SelectAppCurrencyUseCase import com.tangem.domain.appcurrency.SelectAppCurrencyUseCase
@ -20,7 +21,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getAvailableCurrenciesUseCase: GetAvailableCurrenciesUseCase, private val getAvailableCurrenciesUseCase: GetAvailableCurrenciesUseCase,
private val selectAppCurrencyUseCase: SelectAppCurrencyUseCase, private val selectAppCurrencyUseCase: SelectAppCurrencyUseCase,
private val reduxNavController: ReduxNavController, private val router: AppRouter,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel(), AppCurrencySelectorIntents { ) : ViewModel(), AppCurrencySelectorIntents {
@ -34,7 +35,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
val uiState: StateFlow<AppCurrencySelectorState> = stateController.stateFlow val uiState: StateFlow<AppCurrencySelectorState> = stateController.stateFlow
override fun onBackClick() { override fun onBackClick() {
reduxNavController.popBackStack() router.pop()
} }
override fun onSearchClick() { override fun onSearchClick() {
@ -52,7 +53,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
analyticsEventHandler.send( analyticsEventHandler.send(
event = Settings.AppSettings.MainCurrencyChanged(currencyType = currency.name), event = Settings.AppSettings.MainCurrencyChanged(currencyType = currency.name),
) )
reduxNavController.popBackStack() router.pop()
} }
} }
} }

View file

@ -6,10 +6,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.navigation.NavigationAction import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.store import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -36,7 +38,7 @@ internal class AppSettingsFragment : ComposeFragment() {
state = state, state = state,
onBackClick = { onBackClick = {
store.dispatch(DetailsAction.ResetCardSettingsData) store.dispatch(DetailsAction.ResetCardSettingsData)
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
}, },
) )
} }

View file

@ -1,13 +1,17 @@
package com.tangem.tap.features.details.ui.appsettings package com.tangem.tap.features.details.ui.appsettings
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
@ -32,6 +36,7 @@ internal fun AppSettingsScreen(state: AppSettingsScreenState, onBackClick: () ->
}, },
titleRes = R.string.app_settings_title, titleRes = R.string.app_settings_title,
onBackClick = onBackClick, onBackClick = onBackClick,
addBottomInsets = false,
) )
} }
@ -44,7 +49,11 @@ private fun AppSettings(state: AppSettingsScreenState.Content) {
null -> Unit null -> Unit
} }
LazyColumn { val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
LazyColumn(
contentPadding = PaddingValues(bottom = bottomBarHeight),
) {
items( items(
items = state.items, items = state.items,
key = Item::id, key = Item::id,

View file

@ -1,16 +1,20 @@
package com.tangem.tap.features.details.ui.appsettings package com.tangem.tap.features.details.ui.appsettings
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.*
import androidx.lifecycle.LifecycleOwner import com.tangem.common.routing.AppRoute
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.navigation.AppScreen import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.settings.CanUseBiometryUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.features.details.redux.AppSetting import com.tangem.tap.features.details.redux.AppSetting
@ -26,14 +30,22 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.rekotlin.StoreSubscriber import org.rekotlin.StoreSubscriber
import javax.inject.Inject import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel @HiltViewModel
internal class AppSettingsViewModel @Inject constructor( internal class AppSettingsViewModel @Inject constructor(
private val appCurrencyRepository: AppCurrencyRepository, private val appCurrencyRepository: AppCurrencyRepository,
private val walletsRepository: WalletsRepository,
private val canUseBiometryUseCase: CanUseBiometryUseCase,
private val balanceHidingRepository: BalanceHidingRepository,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
private val appThemeModeRepository: AppThemeModeRepository,
private val settingsRepository: SettingsRepository,
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender, private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
private val detailsFeatureToggles: DetailsFeatureToggles,
) : ViewModel(), ) : ViewModel(),
StoreSubscriber<DetailsState>, StoreSubscriber<DetailsState>,
DefaultLifecycleObserver { DefaultLifecycleObserver {
@ -50,6 +62,9 @@ internal class AppSettingsViewModel @Inject constructor(
init { init {
bootstrapAppCurrencyUpdates() bootstrapAppCurrencyUpdates()
if (detailsFeatureToggles.isRedesignEnabled) {
bootstrapBiometricsUpdates()
}
subscribeToStoreChanges() subscribeToStoreChanges()
sendItemsAnalytics() sendItemsAnalytics()
@ -128,7 +143,7 @@ internal class AppSettingsViewModel @Inject constructor(
} }
private fun showAppCurrencySelector() { private fun showAppCurrencySelector() {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.AppCurrencySelector)) store.dispatchNavigationAction { push(AppRoute.AppCurrencySelector) }
} }
private fun showThemeModeSelector(selectedMode: AppThemeMode) { private fun showThemeModeSelector(selectedMode: AppThemeMode) {
@ -214,6 +229,19 @@ internal class AppSettingsViewModel @Inject constructor(
.saveIn(appCurrencyUpdatesJobHolder) .saveIn(appCurrencyUpdatesJobHolder)
} }
private fun bootstrapBiometricsUpdates() = viewModelScope.launch {
val state = AppSettingsState(
saveWallets = walletsRepository.shouldSaveUserWalletsSync(),
saveAccessCodes = settingsRepository.shouldSaveAccessCodes(),
isBiometricsAvailable = canUseBiometryUseCase(),
isHidingEnabled = balanceHidingRepository.getBalanceHidingSettings().isHidingEnabledInSettings,
selectedAppCurrency = appCurrencyRepository.getSelectedAppCurrency().firstOrNull() ?: AppCurrency.Default,
selectedThemeMode = appThemeModeRepository.getAppThemeMode().firstOrNull() ?: AppThemeMode.DEFAULT,
)
store.dispatchWithMain(DetailsAction.AppSettings.Prepare(state))
}
private fun subscribeToStoreChanges() { private fun subscribeToStoreChanges() {
store.subscribe(subscriber = this) { state -> store.subscribe(subscriber = this) { state ->
state.skipRepeats { oldState, newState -> state.skipRepeats { oldState, newState ->

View file

@ -4,9 +4,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.tangem.core.navigation.NavigationAction import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.store import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -29,7 +31,7 @@ internal class CardSettingsFragment : ComposeFragment() {
state = viewModel.screenState.value, state = viewModel.screenState.value,
onBackClick = { onBackClick = {
store.dispatch(DetailsAction.ResetCardSettingsData) store.dispatch(DetailsAction.ResetCardSettingsData)
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
}, },
) )
} }

View file

@ -4,9 +4,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.tangem.core.navigation.NavigationAction import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -28,7 +29,7 @@ class AccessCodeRecoveryFragment : ComposeFragment(), StoreSubscriber<DetailsSta
override fun ScreenContent(modifier: Modifier) { override fun ScreenContent(modifier: Modifier) {
AccessCodeRecoveryScreen( AccessCodeRecoveryScreen(
state = screenState.value, state = screenState.value,
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) }, onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
) )
} }

View file

@ -13,8 +13,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.wallet.R import com.tangem.wallet.R
@Composable @Composable
@ -24,31 +24,42 @@ internal fun SettingsScreensScaffold(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@StringRes titleRes: Int? = null, @StringRes titleRes: Int? = null,
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
addBottomInsets: Boolean = true,
fab: @Composable () -> Unit = {}, fab: @Composable () -> Unit = {},
) { ) {
val state = rememberScaffoldState(snackbarHostState = snackbarHostState) val state = rememberScaffoldState(snackbarHostState = snackbarHostState)
val backgroundColor = TangemTheme.colors.background.secondary val backgroundColor = TangemTheme.colors.background.secondary
BackHandler(onBack = onBackClick) BackHandler(onBack = onBackClick)
SystemBarsEffect {
setSystemBarsColor(backgroundColor)
}
Scaffold( Scaffold(
scaffoldState = state, scaffoldState = state,
topBar = { topBar = {
EmptyTopBarWithNavigation( EmptyTopBarWithNavigation(
modifier = Modifier.statusBarsPadding(),
onBackClick = onBackClick, onBackClick = onBackClick,
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
) )
}, },
modifier = modifier.systemBarsPadding(), modifier = modifier,
contentWindowInsets = WindowInsetsZero,
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
floatingActionButton = fab, floatingActionButton = {
content = { paddings -> Box(modifier = Modifier.navigationBarsPadding()) {
fab()
}
},
content = { paddingValues ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(paddings) .run {
if (addBottomInsets) {
navigationBarsPadding()
} else {
this
}
}
.padding(paddingValues)
.fillMaxSize(), .fillMaxSize(),
) { ) {
if (titleRes != null) { if (titleRes != null) {
@ -81,9 +92,11 @@ internal fun ScreenTitle(titleRes: Int, modifier: Modifier = Modifier) {
@Composable @Composable
internal fun EmptyTopBarWithNavigation( internal fun EmptyTopBarWithNavigation(
onBackClick: () -> Unit, onBackClick: () -> Unit,
modifier: Modifier = Modifier,
backgroundColor: Color = TangemTheme.colors.background.primary, backgroundColor: Color = TangemTheme.colors.background.primary,
) { ) {
TopAppBar( TopAppBar(
modifier = modifier,
title = { }, title = { },
navigationIcon = navigationIcon =
{ {

View file

@ -3,13 +3,14 @@ package com.tangem.tap.features.details.ui.details
import android.os.Bundle import android.os.Bundle
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -45,7 +46,7 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState
DetailsScreen( DetailsScreen(
modifier = modifier, modifier = modifier,
state = detailsViewModel.detailsScreenState.value, state = detailsViewModel.detailsScreenState.value,
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) }, onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
) )
} }

View file

@ -67,7 +67,7 @@ internal sealed class SettingsItem(
data class CardSettings( data class CardSettings(
override val onClick: () -> Unit, override val onClick: () -> Unit,
) : SettingsItem( ) : SettingsItem(
iconResId = R.drawable.ic_card_settings, iconResId = R.drawable.ic_card_settings_24,
title = resourceReference(R.string.card_settings_title), title = resourceReference(R.string.card_settings_title),
) )
@ -95,7 +95,7 @@ internal sealed class SettingsItem(
data class ReferralProgram( data class ReferralProgram(
override val onClick: () -> Unit, override val onClick: () -> Unit,
) : SettingsItem( ) : SettingsItem(
iconResId = R.drawable.ic_add_friends, iconResId = R.drawable.ic_add_friends_24,
title = resourceReference(R.string.details_referral_title), title = resourceReference(R.string.details_referral_title),
) )

View file

@ -3,11 +3,11 @@ package com.tangem.tap.features.details.ui.details
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import com.tangem.common.extensions.guard import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent import com.tangem.core.ui.event.triggeredEvent
@ -16,15 +16,14 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.addContext import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.feedback.FeedbackEmail import com.tangem.tap.common.feedback.FeedbackEmail
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
import com.tangem.tap.features.home.LocaleRegionProvider import com.tangem.tap.features.home.LocaleRegionProvider
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.scope import com.tangem.tap.scope
@ -128,30 +127,38 @@ internal class DetailsViewModel(
} }
private fun navigateToTesterMenu() { private fun navigateToTesterMenu() {
store.state.daggerGraphState.testerRouter?.startTesterScreen() store.dispatchNavigationAction {
push(AppRoute.TesterMenu)
}
} }
private fun navigateToToS() { private fun navigateToToS() {
store.dispatchOnMain(DisclaimerAction.Show(AppScreen.Details)) store.dispatchOnMain(DisclaimerAction.Show(DisclaimerSource.Details))
} }
private fun navigateToReferralProgram() { private fun navigateToReferralProgram() {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.ReferralProgram)) store.dispatchNavigationAction { push(AppRoute.ReferralProgram) }
} }
private fun sendFeedback() { private fun sendFeedback() {
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Settings)) Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Settings))
store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail())) store.dispatchOnMain(
GlobalAction.SendEmail(
feedbackData = FeedbackEmail(),
scanResponse = userWalletsListManager.selectedUserWalletSync?.scanResponse
?: error("ScanResponse must be not null"),
),
)
} }
private fun navigateToAppSettings() { private fun navigateToAppSettings() {
Analytics.send(Settings.ButtonAppSettings()) Analytics.send(Settings.ButtonAppSettings())
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.AppSettings)) store.dispatchNavigationAction { push(AppRoute.AppSettings) }
} }
private fun navigateToCardSettings() { private fun navigateToCardSettings() {
Analytics.send(Settings.ButtonCardSettings()) Analytics.send(Settings.ButtonCardSettings())
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.CardSettings)) store.dispatchNavigationAction { push(AppRoute.CardSettings) }
} }
private fun linkMoreCards() { private fun linkMoreCards() {
@ -164,7 +171,7 @@ internal class DetailsViewModel(
val scanResponse = selectedUserWallet.scanResponse val scanResponse = selectedUserWallet.scanResponse
Analytics.addContext(scanResponse) Analytics.addContext(scanResponse)
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
} }
private fun scanAndSaveUserWallet() { private fun scanAndSaveUserWallet() {
@ -174,12 +181,12 @@ internal class DetailsViewModel(
private fun navigateToWalletConnect() { private fun navigateToWalletConnect() {
Analytics.send(Settings.ButtonWalletConnect()) Analytics.send(Settings.ButtonWalletConnect())
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions)) store.dispatchNavigationAction { push(AppRoute.WalletConnectSessions) }
} }
private fun handleSocialNetworkClick(link: SocialNetworkLink) { private fun handleSocialNetworkClick(link: SocialNetworkLink) {
Analytics.send(Settings.ButtonSocialNetwork(link.network)) Analytics.send(Settings.ButtonSocialNetwork(link.network))
store.dispatchOnMain(NavigationAction.OpenUrl(link.url)) store.dispatchOpenUrl(link.url)
} }
private fun getSocialLinks(): ImmutableList<SocialNetworkLink> { private fun getSocialLinks(): ImmutableList<SocialNetworkLink> {

View file

@ -4,9 +4,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.navigation.NavigationAction import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -30,7 +31,7 @@ internal class ResetCardFragment : ComposeFragment(), StoreSubscriber<DetailsSta
ResetCardScreen( ResetCardScreen(
state = state, state = state,
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) }, onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
modifier = modifier, modifier = modifier,
) )
} }

View file

@ -2,9 +2,9 @@ package com.tangem.tap.features.details.ui.resetcard
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.card.ResetCardUseCase
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
@ -16,6 +16,7 @@ import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.CardSettingsState
@ -190,13 +191,13 @@ internal class ResetCardViewModel @Inject constructor(
val newSelectedWallet = userWalletsListManager.selectedUserWalletSync val newSelectedWallet = userWalletsListManager.selectedUserWalletSync
if (newSelectedWallet != null) { if (newSelectedWallet != null) {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Wallet)) store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
} else { } else {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }.isSuccess val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }.isSuccess
if (isLocked && userWalletsListManager.hasUserWallets) { if (isLocked && userWalletsListManager.hasUserWallets) {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Welcome)) store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
} else { } else {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) store.dispatchNavigationAction { popTo<AppRoute.Home>() }
} }
} }
} }

View file

@ -4,9 +4,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.tangem.core.navigation.NavigationAction import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -29,7 +30,7 @@ internal class SecurityModeFragment : ComposeFragment(), StoreSubscriber<Details
SecurityModeScreen( SecurityModeScreen(
modifier = modifier, modifier = modifier,
state = screenState.value, state = screenState.value,
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) }, onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
) )
} }

View file

@ -6,11 +6,12 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
import com.tangem.tap.store import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -41,7 +42,7 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber<Wallet
modifier = modifier, modifier = modifier,
state = state, state = state,
onBackClick = { onBackClick = {
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
}, },
) )
} }

View file

@ -2,12 +2,10 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs
import android.content.Context import android.content.Context
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tangem.core.navigation.AppScreen import com.tangem.common.routing.AppRoute
import com.tangem.core.navigation.NavigationAction
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.store import com.tangem.tap.store
@ -22,14 +20,9 @@ object ClipboardOrScanQrDialog {
store.dispatch(WalletConnectAction.OpenSession(wcUri)) store.dispatch(WalletConnectAction.OpenSession(wcUri))
} }
setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ -> setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ ->
store.dispatch( store.dispatchNavigationAction {
NavigationAction.NavigateTo( push(AppRoute.QrScanning(source = SourceType.WALLET_CONNECT))
screen = AppScreen.QrScanning, }
bundle = bundleOf(
QrScanningRouter.SOURCE_KEY to SourceType.WALLET_CONNECT,
),
),
)
} }
setOnDismissListener { setOnDismissListener {
store.dispatch(GlobalAction.HideDialog) store.dispatch(GlobalAction.HideDialog)

View file

@ -1,6 +1,5 @@
package com.tangem.tap.features.disclaimer.redux package com.tangem.tap.features.disclaimer.redux
import com.tangem.core.navigation.AppScreen
import com.tangem.tap.common.entities.ProgressState import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.features.disclaimer.Disclaimer import com.tangem.tap.features.disclaimer.Disclaimer
import org.rekotlin.Action import org.rekotlin.Action
@ -10,7 +9,7 @@ sealed class DisclaimerAction : Action {
data class SetDisclaimer(val disclaimer: Disclaimer) : DisclaimerAction() data class SetDisclaimer(val disclaimer: Disclaimer) : DisclaimerAction()
data class Show( data class Show(
val fromScreen: AppScreen, val from: DisclaimerSource,
val callback: DisclaimerCallback? = null, val callback: DisclaimerCallback? = null,
) : DisclaimerAction() ) : DisclaimerAction()

View file

@ -1,7 +1,8 @@
package com.tangem.tap.features.disclaimer.redux package com.tangem.tap.features.disclaimer.redux
import com.tangem.core.navigation.AppScreen import com.tangem.common.routing.AppRoute
import com.tangem.core.navigation.NavigationAction import com.tangem.common.routing.AppRouter
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.mainScope import com.tangem.tap.mainScope
import com.tangem.tap.store import com.tangem.tap.store
@ -25,17 +26,19 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) {
when (action) { when (action) {
is DisclaimerAction.Show -> { is DisclaimerAction.Show -> {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) store.dispatchNavigationAction {
push(AppRoute.Disclaimer(isTosAccepted = action.from == DisclaimerSource.Details))
}
} }
is DisclaimerAction.AcceptDisclaimer -> { is DisclaimerAction.AcceptDisclaimer -> {
mainScope.launch { mainScope.launch {
state.disclaimer.accept() state.disclaimer.accept()
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
state.callback?.onAccept?.invoke() state.callback?.onAccept?.invoke()
} }
} }
is DisclaimerAction.OnBackPressed -> { is DisclaimerAction.OnBackPressed -> {
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
state.callback?.onDismiss?.invoke() state.callback?.onDismiss?.invoke()
} }
} }

View file

@ -17,7 +17,7 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState {
disclaimer = action.disclaimer, disclaimer = action.disclaimer,
) )
is DisclaimerAction.Show -> disclaimerState.copy( is DisclaimerAction.Show -> disclaimerState.copy(
showedFromScreen = action.fromScreen, showedFrom = action.from,
callback = action.callback, callback = action.callback,
) )
is DisclaimerAction.AcceptDisclaimer, is DisclaimerAction.OnBackPressed -> disclaimerState.copy( is DisclaimerAction.AcceptDisclaimer, is DisclaimerAction.OnBackPressed -> disclaimerState.copy(

View file

@ -0,0 +1,5 @@
package com.tangem.tap.features.disclaimer.redux
enum class DisclaimerSource {
Home, Details
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.disclaimer.redux package com.tangem.tap.features.disclaimer.redux
import com.tangem.common.extensions.VoidCallback import com.tangem.common.extensions.VoidCallback
import com.tangem.core.navigation.AppScreen
import com.tangem.tap.common.entities.ProgressState import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.features.disclaimer.Disclaimer import com.tangem.tap.features.disclaimer.Disclaimer
import com.tangem.tap.features.disclaimer.DummyDisclaimer import com.tangem.tap.features.disclaimer.DummyDisclaimer
@ -9,7 +9,7 @@ import org.rekotlin.StateType
data class DisclaimerState( data class DisclaimerState(
val disclaimer: Disclaimer = DummyDisclaimer(), val disclaimer: Disclaimer = DummyDisclaimer(),
val showedFromScreen: AppScreen = AppScreen.Home, val showedFrom: DisclaimerSource = DisclaimerSource.Home,
val callback: DisclaimerCallback? = null, val callback: DisclaimerCallback? = null,
val progressState: ProgressState? = null, val progressState: ProgressState? = null,
) : StateType ) : StateType

View file

@ -7,7 +7,7 @@ import android.webkit.WebView
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.transition.TransitionInflater import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.core.navigation.AppScreen
import com.tangem.core.ui.extensions.setStatusBarColor import com.tangem.core.ui.extensions.setStatusBarColor
import com.tangem.tap.common.entities.ProgressState import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.beginDelayedTransition
@ -18,6 +18,7 @@ import com.tangem.tap.features.BaseFragment
import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.disclaimer.Disclaimer import com.tangem.tap.features.disclaimer.Disclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
import com.tangem.tap.features.disclaimer.redux.DisclaimerState import com.tangem.tap.features.disclaimer.redux.DisclaimerState
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.wallet.R import com.tangem.wallet.R
@ -76,17 +77,14 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs
override fun configureTransitions() { override fun configureTransitions() {
val inflater = TransitionInflater.from(requireContext()) val inflater = TransitionInflater.from(requireContext())
when (store.state.disclaimerState.showedFromScreen) { when (store.state.disclaimerState.showedFrom) {
AppScreen.Home -> { DisclaimerSource.Home -> {
enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom) enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom)
exitTransition = inflater.inflateTransition(android.R.transition.slide_top) exitTransition = inflater.inflateTransition(android.R.transition.slide_top)
} }
AppScreen.Details -> { DisclaimerSource.Details -> {
super.configureTransitions() super.configureTransitions()
} }
else -> {
/* no-op */
}
} }
} }

View file

@ -6,17 +6,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.components.SystemBarsIconsDisposable
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.domain.tokens.TokensAction import com.tangem.domain.tokens.TokensAction
import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.compose.StoriesScreen import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.home.redux.HomeAction
@ -43,15 +42,12 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
@Composable @Composable
override fun ScreenContent(modifier: Modifier) { override fun ScreenContent(modifier: Modifier) {
BackHandler(onBack = requireActivity()::finish) BackHandler(onBack = requireActivity()::finish)
SystemBarsEffect { SystemBarsIconsDisposable(darkIcons = false)
setSystemBarsColor(color = Color.Transparent, darkIcons = false)
}
ScreenContent() ScreenContent()
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, false) }
store.subscribe(subscriber = this) { state -> store.subscribe(subscriber = this) { state ->
state state
@ -86,7 +82,7 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
}, },
onSearchTokensClick = { onSearchTokensClick = {
Analytics.send(IntroductionProcess.ButtonTokensList()) Analytics.send(IntroductionProcess.ButtonTokensList())
store.dispatch(NavigationAction.NavigateTo(AppScreen.ManageTokens)) store.dispatchNavigationAction { push(AppRoute.ManageTokens) }
store.dispatch(TokensAction.SetArgs.ReadAccess) store.dispatch(TokensAction.SetArgs.ReadAccess)
}, },
) )

View file

@ -4,11 +4,10 @@ import com.tangem.common.doOnFailure
import com.tangem.common.doOnResult import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.guard import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.builder.UserWalletBuilder
@ -119,7 +118,7 @@ private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {
scope.launch { store.onUserWalletSelected(userWallet = userWallet) } scope.launch { store.onUserWalletSelected(userWallet = userWallet) }
} }
.doOnResult { .doOnResult {
navigateTo(AppScreen.Wallet) navigateTo(AppRoute.Wallet)
} }
} }
@ -143,8 +142,8 @@ private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
} }
} }
private suspend fun navigateTo(appScreen: AppScreen) { private suspend fun navigateTo(route: AppRoute) {
store.dispatchOnMain(NavigationAction.NavigateTo(appScreen)) store.dispatchNavigationAction { push(route) }
delay(HIDE_PROGRESS_DELAY) delay(HIDE_PROGRESS_DELAY)
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
} }

View file

@ -3,11 +3,10 @@ package com.tangem.tap.features.main
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.ReduxNavController
import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase
import com.tangem.domain.balancehiding.BalanceHidingSettings import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
@ -16,9 +15,11 @@ import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
import com.tangem.domain.staking.FetchStakingTokensUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.features.main.model.MainScreenState import com.tangem.tap.features.main.model.MainScreenState
import com.tangem.tap.store import com.tangem.tap.store
@ -34,7 +35,7 @@ import javax.inject.Inject
internal class MainViewModel @Inject constructor( internal class MainViewModel @Inject constructor(
private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase, private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase,
private val listenToFlipsUseCase: ListenToFlipsUseCase, private val listenToFlipsUseCase: ListenToFlipsUseCase,
private val reduxNavController: ReduxNavController, private val router: AppRouter,
private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase, private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase,
private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase, private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase,
private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase, private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase,
@ -44,6 +45,8 @@ internal class MainViewModel @Inject constructor(
private val sendFeatureToggles: SendFeatureToggles, private val sendFeatureToggles: SendFeatureToggles,
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles, private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val stakingFeatureToggles: StakingFeatureToggles,
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel(), MainIntents { ) : ViewModel(), MainIntents {
@ -70,6 +73,10 @@ internal class MainViewModel @Inject constructor(
displayBalancesHidingStatusToast() displayBalancesHidingStatusToast()
displayHiddenBalancesModalNotification() displayHiddenBalancesModalNotification()
if (stakingFeatureToggles.isStakingEnabled) {
fetchStakingTokens()
}
viewModelScope.launch(dispatchers.main) { viewModelScope.launch(dispatchers.main) {
deleteDeprecatedLogsUseCase() deleteDeprecatedLogsUseCase()
} }
@ -115,6 +122,14 @@ internal class MainViewModel @Inject constructor(
} }
} }
private fun fetchStakingTokens() {
viewModelScope.launch(dispatchers.main) {
fetchStakingTokensUseCase()
.onLeft { Timber.e(it, "Unable to fetch the staking tokens list") }
.onRight { Timber.d("Staking token list was fetched successfully") }
}
}
private fun updateSendFeatureToggle() { private fun updateSendFeatureToggle() {
viewModelScope.launch(dispatchers.main) { viewModelScope.launch(dispatchers.main) {
sendFeatureToggles.fetchNewSendEnabled() sendFeatureToggles.fetchNewSendEnabled()
@ -136,7 +151,7 @@ internal class MainViewModel @Inject constructor(
if (state.value.modalNotification?.isShow != true && !it.isUpdateFromToast) { if (state.value.modalNotification?.isShow != true && !it.isUpdateFromToast) {
listenToFlipsUseCase.changeUpdateEnabled(false) listenToFlipsUseCase.changeUpdateEnabled(false)
stateHolder.updateWithHiddenBalancesNotification() stateHolder.updateWithHiddenBalancesNotification()
reduxNavController.navigate(NavigationAction.NavigateTo(AppScreen.ModalNotification)) router.push(AppRoute.ModalNotification)
} }
} }
.launchIn(viewModelScope) .launchIn(viewModelScope)

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.onboarding package com.tangem.tap.features.onboarding
import com.tangem.common.extensions.VoidCallback import com.tangem.common.extensions.VoidCallback
import com.tangem.core.navigation.StateDialog import com.tangem.domain.redux.StateDialog
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]

View file

@ -3,10 +3,10 @@ package com.tangem.tap.features.onboarding
import com.tangem.common.doOnFailure import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.guard import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
@ -56,18 +56,18 @@ object OnboardingHelper {
} }
} }
fun whereToNavigate(scanResponse: ScanResponse): AppScreen { fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
return when (val type = scanResponse.productType) { return when (val type = scanResponse.productType) {
ProductType.Note -> AppScreen.OnboardingNote ProductType.Note -> AppRoute.OnboardingNote
ProductType.Wallet, ProductType.Wallet,
ProductType.Wallet2, ProductType.Wallet2,
ProductType.Ring, ProductType.Ring,
-> if (scanResponse.card.settings.isBackupAllowed) { -> if (scanResponse.card.settings.isBackupAllowed) {
AppScreen.OnboardingWallet AppRoute.OnboardingWallet()
} else { } else {
AppScreen.OnboardingOther AppRoute.OnboardingOther
} }
ProductType.Twins -> AppScreen.OnboardingTwins ProductType.Twins -> AppRoute.OnboardingTwins
ProductType.Start2Coin, ProductType.Start2Coin,
ProductType.Visa, ProductType.Visa,
-> throw UnsupportedOperationException("Onboarding for ${type.name} cards is not supported") -> throw UnsupportedOperationException("Onboarding for ${type.name} cards is not supported")
@ -111,14 +111,14 @@ object OnboardingHelper {
backupCardsIds = backupCardsIds?.toSet(), backupCardsIds = backupCardsIds?.toSet(),
), ),
) )
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchNavigationAction { push(AppRoute.Wallet) }
delay(timeMillis = 1_800) delay(timeMillis = 1_800)
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet)) store.dispatchNavigationAction { push(AppRoute.SaveWallet) }
} }
// If device has no biometry and save wallet screen has been shown, then go through old scenario // If device has no biometry and save wallet screen has been shown, then go through old scenario
else -> { else -> {
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError) proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchNavigationAction { push(AppRoute.Wallet) }
} }
} }
} }

View file

@ -7,15 +7,20 @@ import androidx.core.view.MenuProvider
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.feedback.SupportInfo import com.tangem.tap.common.feedback.SupportInfo
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.utils.Provider
import com.tangem.wallet.R import com.tangem.wallet.R
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
class OnboardingMenuProvider : MenuProvider { class OnboardingMenuProvider(
private val scanResponseProvider: Provider<ScanResponse>,
) : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_onboarding, menu) menuInflater.inflate(R.menu.menu_onboarding, menu)
} }
@ -24,7 +29,12 @@ class OnboardingMenuProvider : MenuProvider {
R.id.menu_item_chat_support -> { R.id.menu_item_chat_support -> {
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
// changed on email support [REDACTED_TASK_KEY] // changed on email support [REDACTED_TASK_KEY]
store.dispatch(GlobalAction.SendEmail(SupportInfo())) store.dispatch(
GlobalAction.SendEmail(
feedbackData = SupportInfo(),
scanResponse = scanResponseProvider(),
),
)
true true
} }
else -> false else -> false

View file

@ -9,6 +9,8 @@ import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.transitions.HomeToOnboardingTransition import com.tangem.tap.common.transitions.HomeToOnboardingTransition
import com.tangem.tap.features.BaseStoreFragment import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.onboarding.OnboardingMenuProvider import com.tangem.tap.features.onboarding.OnboardingMenuProvider
import com.tangem.tap.store
import com.tangem.utils.Provider
import com.tangem.wallet.R import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentOnboardingMainBinding import com.tangem.wallet.databinding.FragmentOnboardingMainBinding
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
@ -32,7 +34,13 @@ abstract class BaseOnboardingFragment<T> : BaseStoreFragment(R.layout.fragment_o
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar) (activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
} }
override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider() override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
scanResponseProvider = Provider {
store.state.globalState.onboardingState.onboardingManager?.scanResponse
?: store.state.detailsState.scanResponse
?: error("ScanResponse must be not null")
},
)
protected fun showConfetti(show: Boolean) = with(binding.vConfetti) { protected fun showConfetti(show: Boolean) = with(binding.vConfetti) {
lavConfetti.show(show) lavConfetti.show(show)

View file

@ -2,8 +2,9 @@ package com.tangem.tap.features.onboarding.products.note.redux
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.data.tokens.utils.CryptoCurrencyFactory import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.domain.common.extensions.makePrimaryWalletManager import com.tangem.domain.common.extensions.makePrimaryWalletManager
import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.extensions.withMainContext
@ -204,7 +205,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
OnboardingDialog.InterruptOnboarding( OnboardingDialog.InterruptOnboarding(
onOk = { onOk = {
OnboardingHelper.onInterrupted() OnboardingHelper.onInterrupted()
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
}, },
), ),
) )

View file

@ -3,9 +3,10 @@ package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.Result
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.data.tokens.utils.CryptoCurrencyFactory import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.domain.common.extensions.makePrimaryWalletManager import com.tangem.domain.common.extensions.makePrimaryWalletManager
import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.extensions.withMainContext
@ -39,6 +40,7 @@ import kotlinx.coroutines.runBlocking
import org.rekotlin.Action import org.rekotlin.Action
import org.rekotlin.DispatchFunction import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware import org.rekotlin.Middleware
import kotlin.reflect.KClass
object TwinCardsMiddleware { object TwinCardsMiddleware {
val handler = twinsWalletMiddleware val handler = twinsWalletMiddleware
@ -328,7 +330,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
if (walletsRepository.shouldSaveUserWalletsSync()) { if (walletsRepository.shouldSaveUserWalletsSync()) {
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
} else { } else {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) store.dispatchNavigationAction { popTo<AppRoute.Home>() }
} }
} }
} }
@ -352,7 +354,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
store.dispatch(TwinCardsAction.CardsManager.Release) store.dispatch(TwinCardsAction.CardsManager.Release)
action.shouldResetTwinCardsWidget(shouldReturnCardBack) { action.shouldResetTwinCardsWidget(shouldReturnCardBack) {
store.dispatchOnMain(NavigationAction.PopBackTo(getPopBackScreen())) store.dispatchNavigationAction { popTo(routeClass = getPopBackScreen()) }
} }
} }
store.dispatchDialogShow(OnboardingDialog.InterruptOnboarding(onOkCallback)) store.dispatchDialogShow(OnboardingDialog.InterruptOnboarding(onOkCallback))
@ -362,18 +364,19 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
} }
} }
private fun getPopBackScreen(): AppScreen { private fun getPopBackScreen(): KClass<out AppRoute> {
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
return if (userWalletsListManager.hasUserWallets) { return if (userWalletsListManager.hasUserWallets) {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync } val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
.fold(onSuccess = { true }, onFailure = { false }) .fold(onSuccess = { true }, onFailure = { false })
if (isLocked) { if (isLocked) {
AppScreen.Welcome AppRoute.Welcome::class
} else { } else {
AppScreen.Wallet AppRoute.Wallet::class
} }
} else { } else {
AppScreen.Home AppRoute.Home::class
} }
} }

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.onboarding.products.twins.redux package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.getTwinCardNumber import com.tangem.domain.common.getTwinCardNumber
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action import org.rekotlin.Action
@ -35,7 +35,10 @@ private fun internalReduce(action: Action, state: AppState): TwinCardsState {
) )
} }
is TwinCardsAction.SetStepOfScreen -> { is TwinCardsAction.SetStepOfScreen -> {
state = state.copy(currentStep = action.step) state = state.copy(
currentStep = action.step,
welcomeOnlyScanResponse = (action.step as? TwinCardsStep.WelcomeOnly)?.scanResponse,
)
} }
is TwinCardsAction.SetUserUnderstand -> { is TwinCardsAction.SetUserUnderstand -> {
state = state.copy(userWasUnderstandIfWalletRecreate = action.isUnderstand) state = state.copy(userWasUnderstandIfWalletRecreate = action.isUnderstand)

View file

@ -1,8 +1,8 @@
package com.tangem.tap.features.onboarding.products.twins.redux package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManager
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.onboarding.OnboardingWalletBalance import com.tangem.tap.features.onboarding.OnboardingWalletBalance
@ -29,6 +29,7 @@ data class TwinCardsState(
val balanceNonCriticalError: TapError? = null, val balanceNonCriticalError: TapError? = null,
val balanceCriticalError: TapError? = null, val balanceCriticalError: TapError? = null,
val showConfetti: Boolean = false, val showConfetti: Boolean = false,
val welcomeOnlyScanResponse: ScanResponse? = null,
) : StateType { ) : StateType {
val steps: List<TwinCardsStep> val steps: List<TwinCardsStep>

View file

@ -6,6 +6,7 @@ import android.view.View
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.MenuProvider
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import coil.load import coil.load
@ -25,12 +26,14 @@ import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
import com.tangem.tap.domain.twins.TwinsCardWidget import com.tangem.tap.domain.twins.TwinsCardWidget
import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.utils.Provider
import com.tangem.wallet.R import com.tangem.wallet.R
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -61,6 +64,15 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
} }
} }
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
scanResponseProvider = Provider {
store.state.twinCardsState.welcomeOnlyScanResponse
?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
?: store.state.detailsState.scanResponse
?: error("ScanResponse must be not null")
},
)
@Suppress("MagicNumber") @Suppress("MagicNumber")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View file

@ -7,16 +7,16 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard import com.tangem.common.extensions.guard
import com.tangem.common.extensions.ifNotNull import com.tangem.common.extensions.ifNotNull
import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toHexString
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.services.Result import com.tangem.common.services.Result
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.Artwork
import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.models.Artwork
import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator
@ -25,10 +25,7 @@ import com.tangem.operations.backup.BackupService
import com.tangem.tap.* import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
@ -164,7 +161,7 @@ private fun handleWalletAction(action: Action) {
store.dispatch(GlobalAction.Onboarding.Stop) store.dispatch(GlobalAction.Onboarding.Stop)
if (scanResponse == null) { if (scanResponse == null) {
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
store.dispatch(HomeAction.ReadCard(scope = action.scope)) store.dispatch(HomeAction.ReadCard(scope = action.scope))
} else { } else {
val backupState = store.state.onboardingWalletState.backupState val backupState = store.state.onboardingWalletState.backupState
@ -529,7 +526,8 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
addedBackupCardsCount = backupService.addedBackupCardsCount, addedBackupCardsCount = backupService.addedBackupCardsCount,
), ),
) )
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
} }
is BackupAction.SkipBackup -> { is BackupAction.SkipBackup -> {
Analytics.send(Onboarding.Backup.Skipped()) Analytics.send(Onboarding.Backup.Skipped())
@ -614,7 +612,7 @@ private fun handleOnBackPressed(state: OnboardingWalletState) {
} }
BackupStep.Finished -> { BackupStep.Finished -> {
OnboardingHelper.onInterrupted() OnboardingHelper.onInterrupted()
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
} }
} }
} }
@ -625,7 +623,7 @@ private fun showInterruptOnboardingDialog() {
onOk = { onOk = {
OnboardingHelper.onInterrupted() OnboardingHelper.onInterrupted()
store.dispatch(BackupAction.DiscardBackup) store.dispatch(BackupAction.DiscardBackup)
store.dispatch(NavigationAction.PopBackTo()) store.dispatchNavigationAction(AppRouter::pop)
}, },
), ),
) )

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import com.tangem.core.navigation.StateDialog import com.tangem.domain.redux.StateDialog
import org.rekotlin.StateType import org.rekotlin.StateType
/** /**

Some files were not shown because too many files have changed in this diff Show more