Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-24 10:50:03 +01:00
commit bc84d8f5f8
579 changed files with 13604 additions and 3422 deletions

View file

@ -1,6 +1,8 @@
package com.tangem.feature.wallet.di
import com.tangem.core.navigation.ReduxNavController
import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.feature.wallet.presentation.router.DefaultWalletRouter
import com.tangem.features.wallet.navigation.WalletRouter
import dagger.Module
@ -15,7 +17,11 @@ internal object WalletRouterModule {
@Provides
@ActivityScoped
fun provideWalletRouter(reduxNavController: ReduxNavController): WalletRouter {
return DefaultWalletRouter(reduxNavController = reduxNavController)
fun provideWalletRouter(
appRouter: AppRouter,
urlOpener: UrlOpener,
reduxStateHolder: ReduxStateHolder,
): WalletRouter {
return DefaultWalletRouter(appRouter, urlOpener, reduxStateHolder)
}
}

View file

@ -3,8 +3,6 @@ package com.tangem.feature.wallet.presentation
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.screen.ComposeFragment
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.features.managetokens.navigation.ManageTokensUi
@ -37,11 +35,6 @@ internal class WalletFragment : ComposeFragment() {
@Composable
override fun ScreenContent(modifier: Modifier) {
val systemBarsColor = TangemTheme.colors.background.secondary
SystemBarsEffect {
setSystemBarsColor(systemBarsColor)
}
_walletRouter.Initialize(
onFinish = requireActivity()::finish,
manageTokensUi = manageTokensUi,

View file

@ -8,10 +8,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.common.Strings
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.utils.Strings
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.CryptoAmountState as TokenCryptoAmountState
@Composable

View file

@ -7,9 +7,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.common.Strings
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.res.TangemTheme
import com.tangem.utils.Strings
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.FiatAmountState as TokenFiatAmountState
@Composable

View file

@ -16,11 +16,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
@ -28,6 +27,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
@ -36,6 +36,7 @@ import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.component.DraggableNetworkGroupItem
@ -57,8 +58,12 @@ internal fun OrganizeTokensScreen(state: OrganizeTokensState, modifier: Modifier
Scaffold(
modifier = modifier,
topBar = {
TopBar(state.header, tokensListState)
TopBar(
config = state.header,
tokensListState = tokensListState,
)
},
contentWindowInsets = WindowInsetsZero,
content = { paddingValues ->
TokenList(
modifier = Modifier
@ -72,7 +77,9 @@ internal fun OrganizeTokensScreen(state: OrganizeTokensState, modifier: Modifier
},
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
Actions(state.actions)
Box(modifier = Modifier.navigationBarsPadding()) {
Actions(state.actions)
}
},
containerColor = TangemTheme.colors.background.secondary,
)
@ -104,9 +111,11 @@ private fun TokenList(
onDragEnd = onDragEnd,
)
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
val listContentPadding = PaddingValues(
top = TangemTheme.dimens.spacing4,
bottom = TangemTheme.dimens.spacing92,
bottom = TangemTheme.dimens.spacing92 + bottomBarHeight,
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
)
@ -140,7 +149,7 @@ private fun TokenList(
}
}
BottomGradient(modifier = Modifier.align(Alignment.BottomCenter))
BottomFade(modifier = Modifier.align(Alignment.BottomCenter))
}
}
@ -191,23 +200,6 @@ private fun LazyItemScope.DraggableItem(
}
}
@Composable
private fun BottomGradient(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size116)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
TangemTheme.colors.background.secondary,
),
),
),
)
}
@Composable
private fun TopBar(
config: OrganizeTokensState.HeaderConfig,
@ -228,6 +220,7 @@ private fun TopBar(
modifier = modifier
.shadow(elevation)
.background(TangemTheme.colors.background.secondary)
.statusBarsPadding()
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
) {

View file

@ -1,12 +1,9 @@
package com.tangem.feature.wallet.presentation.router
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -16,27 +13,27 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.ReduxNavController
import com.tangem.core.navigation.StateDialog
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.onboarding.navigation.OnboardingRouter
import com.tangem.feature.wallet.presentation.WalletFragment
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.managetokens.navigation.ExpandableState
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import kotlin.properties.Delegates
/** Default implementation of wallet feature router */
internal class DefaultWalletRouter(
private val reduxNavController: ReduxNavController,
private val router: AppRouter,
private val urlOpener: UrlOpener,
private val reduxStateHolder: ReduxStateHolder,
) : InnerWalletRouter {
private var navController: NavHostController by Delegates.notNull()
@ -87,7 +84,6 @@ internal class DefaultWalletRouter(
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
OrganizeTokensScreen(
modifier = Modifier.statusBarsPadding(),
state = uiState,
)
}
@ -95,7 +91,7 @@ internal class DefaultWalletRouter(
}
@SuppressLint("RestrictedApi")
override fun popBackStack(screen: AppScreen?) {
override fun popBackStack() {
/*
* It's hack that avoid issue with closing the wallet screen.
* We are using NavGraph only inside feature so first backstack's element is entry of NavGraph and
@ -103,11 +99,7 @@ internal class DefaultWalletRouter(
* If backstack contains only NavGraph entry and wallet screen entry then we close the wallet fragment.
*/
if (navController.currentBackStack.value.size == BACKSTACK_ENTRY_COUNT_TO_CLOSE_WALLET_SCREEN) {
if (screen != null) {
reduxNavController.navigate(action = NavigationAction.PopBackTo(screen))
} else {
onFinish.invoke()
}
onFinish.invoke()
} else {
navController.popBackStack()
}
@ -118,64 +110,53 @@ internal class DefaultWalletRouter(
}
override fun openDetailsScreen(selectedWalletId: UserWalletId) {
reduxNavController.navigate(
action = NavigationAction.NavigateTo(
screen = AppScreen.Details,
bundle = bundleOf(
DetailsEntryPoint.USER_WALLET_ID_KEY to selectedWalletId.stringValue,
),
router.push(
AppRoute.Details(
userWalletId = selectedWalletId,
),
)
}
override fun openOnboardingScreen() {
reduxNavController.navigate(
action = NavigationAction.NavigateTo(
screen = AppScreen.OnboardingWallet,
bundle = bundleOf(OnboardingRouter.CAN_SKIP_BACKUP to false),
),
router.push(
AppRoute.OnboardingWallet(canSkipBackup = false),
)
}
override fun openUrl(url: String) {
reduxNavController.navigate(action = NavigationAction.OpenUrl(url))
urlOpener.openUrl(url)
}
override fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
val networkAddress = currencyStatus.value.networkAddress
if (networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()) {
reduxNavController.navigate(
action = NavigationAction.NavigateTo(
screen = AppScreen.WalletDetails,
bundle = bundleOf(
TokenDetailsRouter.USER_WALLET_ID_KEY to userWalletId.stringValue,
TokenDetailsRouter.CRYPTO_CURRENCY_KEY to currencyStatus.currency,
),
router.push(
AppRoute.CurrencyDetails(
userWalletId = userWalletId,
currency = currencyStatus.currency,
),
)
}
}
override fun openStoriesScreen() {
reduxNavController.navigate(action = NavigationAction.NavigateTo(screen = AppScreen.Home))
router.push(AppRoute.Home)
}
override fun openSaveUserWalletScreen() {
reduxNavController.navigate(action = NavigationAction.NavigateTo(AppScreen.SaveWallet))
router.push(AppRoute.SaveWallet)
}
override fun isWalletLastScreen(): Boolean = reduxNavController.getBackStack().lastOrNull() == AppScreen.Wallet
override fun isWalletLastScreen(): Boolean {
return router.stack.lastOrNull() is AppRoute.Wallet
}
override fun openManageTokensScreen() {
reduxNavController.navigate(action = NavigationAction.NavigateTo(AppScreen.ManageTokens))
router.push(AppRoute.ManageTokens)
}
override fun openScanFailedDialog() {
reduxNavController.navigate(
action = NavigationAction.OpenDialog(
StateDialog.ScanFailsDialog(StateDialog.ScanFailsSource.MAIN),
),
)
override fun openScanFailedDialog(onTryAgain: () -> Unit) {
reduxStateHolder.dispatchDialogShow(StateDialog.ScanFailsDialog(StateDialog.ScanFailsSource.MAIN, onTryAgain))
}
private companion object {

View file

@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.router
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import com.tangem.core.navigation.AppScreen
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.managetokens.navigation.ManageTokensUi
@ -29,7 +29,7 @@ internal interface InnerWalletRouter : WalletRouter {
fun Initialize(onFinish: () -> Unit, manageTokensUi: ManageTokensUi)
/** Pop back stack */
fun popBackStack(screen: AppScreen? = null)
fun popBackStack()
/** Open organize tokens screen */
fun openOrganizeTokensScreen(userWalletId: UserWalletId)
@ -59,5 +59,5 @@ internal interface InnerWalletRouter : WalletRouter {
fun openManageTokensScreen()
/** Open scan failed dialog */
fun openScanFailedDialog()
fun openScanFailedDialog(onTryAgain: () -> Unit)
}

View file

@ -0,0 +1,11 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
data class PushNotificationsBottomSheetConfig(
val isFirstTimeAsking: Boolean,
val onRequest: () -> Unit,
val onAllow: () -> Unit,
val onDeny: () -> Unit,
val openSettings: () -> Unit,
) : TangemBottomSheetConfigContent

View file

@ -2,9 +2,9 @@ package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.common.Strings
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.Strings
/** Wallet card state */
@Immutable

View file

@ -89,6 +89,19 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
),
)
data class Stake(
override val enabled: Boolean,
override val dimContent: Boolean,
override val onClick: () -> Unit,
) : WalletManageButton(
config = ActionButtonConfig(
text = TextReference.Res(id = R.string.common_stake),
iconResId = R.drawable.ic_arrow_down_24, // TODO staking
onClick = onClick,
dimContent = dimContent,
),
)
/**
* Sell
*

View file

@ -19,24 +19,25 @@ internal class SetBalancesAndLimitsTransformer(
private val userWallet: UserWallet,
private val maybeVisaCurrency: Either<Throwable, VisaCurrency>,
private val clickIntents: WalletClickIntents,
) : WalletStateTransformer(userWallet.walletId) {
) : TypedWalletStateTransformer<WalletState.Visa.Content>(
userWalletId = userWallet.walletId,
targetStateClass = WalletState.Visa.Content::class,
) {
override fun transform(prevState: WalletState): WalletState {
return prevState.transformWhenInState<WalletState.Visa.Content> { state ->
val visaCurrency = maybeVisaCurrency.getOrElse {
return state.copy(
walletCardState = getErrorWalletCardState(state.walletCardState),
depositButtonState = state.depositButtonState.copy(isEnabled = false),
balancesAndLimitBlockState = BalancesAndLimitsBlockState.Error,
)
}
state.copy(
walletCardState = getContentWalletCardState(state.walletCardState, visaCurrency),
depositButtonState = state.depositButtonState.copy(isEnabled = true),
balancesAndLimitBlockState = getContentBlockState(visaCurrency),
override fun transformTyped(prevState: WalletState.Visa.Content): WalletState {
val visaCurrency = maybeVisaCurrency.getOrElse {
return prevState.copy(
walletCardState = getErrorWalletCardState(prevState.walletCardState),
depositButtonState = prevState.depositButtonState.copy(isEnabled = false),
balancesAndLimitBlockState = BalancesAndLimitsBlockState.Error,
)
}
return prevState.copy(
walletCardState = getContentWalletCardState(prevState.walletCardState, visaCurrency),
depositButtonState = prevState.depositButtonState.copy(isEnabled = true),
balancesAndLimitBlockState = getContentBlockState(visaCurrency),
)
}
private fun getContentBlockState(visaCurrency: VisaCurrency) = BalancesAndLimitsBlockState.Content(

View file

@ -64,6 +64,7 @@ internal class SetRefreshStateTransformer(
is WalletManageButton.Send -> button.copy(enabled = isButtonsEnabled)
is WalletManageButton.Sell -> button.copy(enabled = isButtonsEnabled)
is WalletManageButton.Receive -> button
is WalletManageButton.Stake -> null
is WalletManageButton.Swap -> null
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import kotlin.reflect.KClass
internal abstract class TypedWalletStateTransformer<S : WalletState>(
userWalletId: UserWalletId,
protected val targetStateClass: KClass<S>,
) : WalletStateTransformer(userWalletId) {
abstract fun transformTyped(prevState: S): WalletState
@Suppress("UNCHECKED_CAST")
final override fun transform(prevState: WalletState): WalletState {
return if (prevState::class == targetStateClass) {
transformTyped(prevState as S)
} else {
prevState
}
}
}

View file

@ -4,7 +4,6 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import kotlinx.collections.immutable.toImmutableList
import timber.log.Timber
internal abstract class WalletStateTransformer(
protected val userWalletId: UserWalletId,
@ -12,7 +11,7 @@ internal abstract class WalletStateTransformer(
abstract fun transform(prevState: WalletState): WalletState
override fun transform(prevState: WalletScreenState): WalletScreenState {
final override fun transform(prevState: WalletScreenState): WalletScreenState {
return prevState.copy(
wallets = prevState.wallets
.map { state ->
@ -21,13 +20,4 @@ internal abstract class WalletStateTransformer(
.toImmutableList(),
)
}
protected inline fun <reified S : WalletState> WalletState.transformWhenInState(
transform: (state: S) -> WalletState,
): WalletState = if (this is S) {
transform(this)
} else {
Timber.w("Impossible to transform ${this::class.simpleName} because current is ${S::class.simpleName}")
this
}
}

View file

@ -59,6 +59,11 @@ internal class MultiWalletCurrencyActionsConverter(
icon = R.drawable.ic_arrow_down_24
action = { clickIntents.onReceiveClick(cryptoCurrencyStatus) }
}
is TokenActionsState.ActionState.Stake -> {
title = resourceReference(R.string.common_stake)
icon = R.drawable.ic_arrow_down_24 // TODO staking replace icon
action = { clickIntents.onStakeClick(cryptoCurrencyStatus) }
}
is TokenActionsState.ActionState.Sell -> {
title = resourceReference(R.string.common_sell)
icon = R.drawable.ic_currency_24

View file

@ -9,6 +9,7 @@ import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.visa.model.VisaTxHistoryItem
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.VisaWalletIntents
import com.tangem.utils.Strings
import com.tangem.utils.converter.Converter
import org.joda.time.DateTimeZone
@ -20,7 +21,7 @@ internal class VisaTxHistoryItemStateConverter(
override fun convert(value: VisaTxHistoryItem): TransactionState {
val localDate = value.date.withZone(DateTimeZone.getDefault())
val time = DateTimeFormatters.formatDate(localDate, DateTimeFormatters.timeFormatter)
val subtitle = "$time ${value.status.capitalize()}"
val subtitle = "$time ${Strings.DOT} ${value.status.capitalize()}"
return TransactionState.Content(
txHash = value.id,

View file

@ -20,6 +20,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
@ -33,16 +34,14 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.paging.compose.collectAsLazyPagingItems
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.tangem.core.ui.components.Keyboard
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheet
import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig
import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
import com.tangem.core.ui.components.snackbar.TangemSnackbar
import com.tangem.core.ui.components.transactions.state.TxHistoryState
@ -50,10 +49,12 @@ import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.walletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder
import com.tangem.feature.wallet.presentation.wallet.ui.components.PushNotificationsBottomSheet
import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
@ -112,7 +113,7 @@ internal fun WalletScreen(
)
}
@Suppress("LongMethod", "LongParameterList")
@Suppress("LongMethod", "LongParameterList", "CyclomaticComplexMethod")
@Composable
private fun WalletContent(
state: WalletScreenState,
@ -144,17 +145,20 @@ private fun WalletContent(
.padding(top = betweenItemsPadding)
.padding(horizontal = horizontalPadding)
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
LazyColumn(
modifier = Modifier
.fillMaxSize()
.testTag(TestTags.WALLET_SCREEN),
contentPadding = PaddingValues(
bottom = TangemTheme.dimens.spacing92,
bottom = TangemTheme.dimens.spacing92 + bottomBarHeight,
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
item(
key = state.wallets.map { it.walletCardState.id },
// !!! Type of the key should be saveable via Bundle on Android !!!
key = state.wallets.map { it.walletCardState.id.stringValue },
contentType = state.wallets.map { it.walletCardState.id },
) {
WalletsList(
@ -202,17 +206,7 @@ private fun WalletContent(
organizeTokens(state = selectedWallet, itemModifier = itemModifier)
}
val bottomSheetConfig = selectedWallet.bottomSheetConfig
if (bottomSheetConfig != null) {
when (bottomSheetConfig.content) {
is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig)
is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig)
is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig)
is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig)
is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig)
is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig)
}
}
ShowBottomSheet(bottomSheetConfig = selectedWallet.bottomSheetConfig)
WalletsListEffects(
lazyListState = walletsListState,
@ -329,7 +323,7 @@ private fun BaseScaffoldManageTokenRedesign(
coroutineScope.launch { bottomSheetState.partialExpand() }
}
},
content = { paddingValues ->
content = { _ ->
val pullRefreshState = rememberPullRefreshState(
refreshing = selectedWallet.pullToRefreshConfig.isRefreshing,
onRefresh = {
@ -337,9 +331,7 @@ private fun BaseScaffoldManageTokenRedesign(
},
)
Column(
modifier = Modifier.padding(paddingValues),
) {
Column {
WalletTopBar(config = state.topBarConfig)
Box(
modifier = Modifier.pullRefresh(pullRefreshState),
@ -391,7 +383,7 @@ private fun BottomSheetScrim(color: Color, visible: Boolean, onDismissRequest: (
}
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("CyclomaticComplexMethod")
@Suppress("CyclomaticComplexMethod", "MagicNumber")
@Composable
private fun BottomSheetStateEffects(
bottomSheetState: SheetState,
@ -424,19 +416,29 @@ private fun BottomSheetStateEffects(
val systemUiController = rememberSystemUiController()
val navigationBarColor = TangemTheme.colors.background.primary
val navigationBarColorWithout = TangemTheme.colors.background.secondary
SystemBarsEffect {
if (showManageTokensBottomSheet) {
setNavigationBarColor(navigationBarColor)
LaunchedEffect(key1 = bottomSheetState.targetValue, navigationBarColor) {
when (bottomSheetState.targetValue) {
SheetValue.Hidden,
SheetValue.Expanded,
-> systemUiController.setNavigationBarColor(
color = Color.Transparent,
darkIcons = navigationBarColor.luminance() > 0.5f,
navigationBarContrastEnforced = true,
)
SheetValue.PartiallyExpanded,
-> systemUiController.setNavigationBarColor(navigationBarColor)
}
}
DisposableEffect(
showManageTokensBottomSheet,
) {
DisposableEffect(showManageTokensBottomSheet) {
onDispose {
if (showManageTokensBottomSheet) {
systemUiController.setNavigationBarColor(navigationBarColorWithout)
systemUiController.setNavigationBarColor(
color = Color.Transparent,
darkIcons = navigationBarColor.luminance() > 0.5f,
navigationBarContrastEnforced = false,
)
}
}
}
@ -505,6 +507,7 @@ private fun BaseScaffold(
) {
Scaffold(
topBar = { WalletTopBar(config = state.topBarConfig) },
contentWindowInsets = WindowInsetsZero,
snackbarHost = {
WalletSnackbarHost(
snackbarHostState = snackbarHostState,
@ -519,7 +522,12 @@ private fun BaseScaffold(
)
}
manageTokensButtonConfig?.let { ManageTokensButton(onClick = it.onClick) }
manageTokensButtonConfig?.let {
ManageTokensButton(
modifier = Modifier.navigationBarsPadding(),
onClick = it.onClick,
)
}
},
floatingActionButtonPosition = FabPosition.Center,
containerColor = TangemTheme.colors.background.secondary,
@ -543,6 +551,8 @@ private fun BaseScaffold(
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
)
BottomFade(Modifier.align(Alignment.BottomCenter))
}
},
)
@ -564,11 +574,11 @@ private fun WalletSnackbarHost(
}
@Composable
private fun ManageTokensButton(onClick: () -> Unit) {
private fun ManageTokensButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
PrimaryButton(
text = stringResource(id = R.string.main_manage_tokens),
onClick = onClick,
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing16),
)
@ -588,6 +598,21 @@ internal fun LazyListScope.organizeTokens(state: WalletState, itemModifier: Modi
}
}
@Composable
private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
if (bottomSheetConfig != null) {
when (bottomSheetConfig.content) {
is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig)
is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig)
is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig)
is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig)
is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig)
is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig)
is PushNotificationsBottomSheetConfig -> PushNotificationsBottomSheet(config = bottomSheetConfig)
}
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)

View file

@ -0,0 +1,119 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.showcase.ShowcaseContent
import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.requestPushPermission
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun PushNotificationsBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet<PushNotificationsBottomSheetConfig>(config = config) {
PushNotificationsSheetContent(content = it, onDismiss = config.onDismissRequest)
}
}
@Composable
private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetConfig, onDismiss: () -> Unit) {
val isClicked = remember { mutableStateOf(false) }
val requestPushPermission = requestPushPermission(
pushPermission = getPushPermissionOrNull(),
isFirstTimeAsking = content.isFirstTimeAsking,
isClicked = isClicked,
onAllow = {
content.onAllow()
onDismiss()
},
onDeny = {
content.onDeny()
onDismiss()
},
onOpenSettings = content.openSettings,
)
Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
ShowcaseContent(
headerIconRes = R.drawable.ic_notifications_unread_24,
headerText = resourceReference(R.string.user_push_notification_agreement_header),
showcaseItems = persistentListOf(
ShowcaseItemModel(
iconRes = R.drawable.ic_rocket_launch_24,
text = resourceReference(R.string.user_push_notification_agreement_argument_one),
),
ShowcaseItemModel(
iconRes = R.drawable.ic_storefront_24,
text = resourceReference(R.string.user_push_notification_agreement_argument_two),
),
),
modifier = Modifier.padding(top = TangemTheme.dimens.spacing40),
)
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
modifier = Modifier.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
top = TangemTheme.dimens.spacing40,
bottom = TangemTheme.dimens.spacing16,
),
) {
SecondaryButton(
text = stringResource(R.string.common_later),
onClick = {
content.onDeny()
onDismiss()
},
modifier = Modifier.weight(1f),
)
PrimaryButton(
text = stringResource(R.string.common_allow),
onClick = {
isClicked.value = true
content.onRequest()
requestPushPermission()
},
modifier = Modifier.weight(1f),
)
}
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PushNotificationsSheetContent_Preview() {
TangemThemePreview {
PushNotificationsSheetContent(
PushNotificationsBottomSheetConfig(
isFirstTimeAsking = false,
onRequest = {},
onAllow = {},
onDeny = {},
openSettings = {},
),
onDismiss = {},
)
}
}
// endregion

View file

@ -42,7 +42,6 @@ import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.ConstraintLayoutScope
import androidx.constraintlayout.compose.Dimension
import com.tangem.common.Strings
import com.tangem.core.ui.components.FontSizeRange
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.ResizableText
@ -54,6 +53,7 @@ import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.utils.Strings
private const val HALF_OF_ITEM_WIDTH = 0.5

View file

@ -4,11 +4,11 @@ import androidx.compose.runtime.MutableState
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.navigation.settings.SettingsManager
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.settings.CanUseBiometryUseCase
import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled
import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase
import com.tangem.domain.settings.*
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
@ -19,6 +19,7 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWal
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.DemonstrateWalletsScrollPreview.Direction
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletPullToRefreshConfig
@ -28,6 +29,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSend
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
import com.tangem.features.managetokens.navigation.ExpandableState
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
@ -55,12 +57,16 @@ internal class WalletViewModel @Inject constructor(
private val canUseBiometryUseCase: CanUseBiometryUseCase,
private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
analyticsEventsHandler: AnalyticsEventHandler,
private val dispatchers: CoroutineDispatcherProvider,
private val screenLifecycleProvider: ScreenLifecycleProvider,
private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender,
private val walletDeepLinksHandler: WalletDeepLinksHandler,
private val walletNameMigrationUseCase: WalletNameMigrationUseCase,
private val shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase,
private val isFirstTimeAskingPermissionUseCase: IsFirstTimeAskingPermissionUseCase,
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
private val settingsManager: SettingsManager,
analyticsEventsHandler: AnalyticsEventHandler,
) : ViewModel() {
val uiState: StateFlow<WalletScreenState> = stateHolder.uiState
@ -80,6 +86,7 @@ internal class WalletViewModel @Inject constructor(
subscribeOnBalanceHiding()
subscribeOnSelectedWalletFlow()
subscribeToScreenBackgroundState()
subscribeOnPushNotificationsPermission()
}
private fun maybeMigrateNames() {
@ -145,6 +152,31 @@ internal class WalletViewModel @Inject constructor(
.launchIn(viewModelScope)
}
private fun subscribeOnPushNotificationsPermission() {
viewModelScope.launch {
if (!shouldAskPermissionUseCase(PUSH_PERMISSION)) return@launch
delay(timeMillis = 1_800)
val isFirstTimeAsking = isFirstTimeAskingPermissionUseCase(PUSH_PERMISSION).getOrElse { true }
val wasInitiallyAsk = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { true }
val onDenyClick: () -> Unit = if (wasInitiallyAsk) {
clickIntents::onDelayAskPushPermission
} else {
clickIntents::onNeverAskPushPermission
}
stateHolder.showBottomSheet(
PushNotificationsBottomSheetConfig(
isFirstTimeAsking = isFirstTimeAsking,
onRequest = clickIntents::onRequestPushPermission,
onAllow = clickIntents::onNeverAskPushPermission,
onDeny = onDenyClick,
openSettings = settingsManager::openSettings,
),
)
}
}
private fun subscribeOnSelectedWalletFlow() {
getSelectedWalletUseCase().onRight {
it

View file

@ -1,21 +1,16 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.wallets.usecase.GetWalletNamesUseCase
import com.tangem.domain.wallets.usecase.RenameWalletUseCase
import com.tangem.feature.wallet.impl.R
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.ReduxNavController
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
@ -52,7 +47,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor(
private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val reduxStateHolder: ReduxStateHolder,
private val reduxNavController: ReduxNavController,
private val appRouter: AppRouter,
private val dispatchers: CoroutineDispatcherProvider,
) : BaseWalletClickIntents(), WalletCardClickIntents {
@ -123,7 +118,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor(
reduxStateHolder.onUserWalletSelected(selectedWallet)
} else {
stateHolder.clear()
reduxNavController.navigate(NavigationAction.PopBackTo(AppScreen.Home))
appRouter.replaceAll(AppRoute.Home)
}
}
}

View file

@ -30,6 +30,7 @@ internal class WalletClickIntents @Inject constructor(
private val currencyActionsClickIntentsImplementor: WalletCurrencyActionsClickIntentsImplementor,
private val contentClickIntentsImplementor: WalletContentClickIntentsImplementor,
private val visaWalletIntentsImplementor: VisaWalletIntentsImplementor,
private val pushPermissionClickIntentsImplementor: WalletPushPermissionClickIntentsImplementor,
private val stateHolder: WalletStateController,
private val walletScreenContentLoader: WalletScreenContentLoader,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
@ -45,7 +46,8 @@ internal class WalletClickIntents @Inject constructor(
WalletWarningsClickIntents by warningsClickIntentsImplementer,
WalletCurrencyActionsClickIntents by currencyActionsClickIntentsImplementor,
WalletContentClickIntents by contentClickIntentsImplementor,
VisaWalletIntents by visaWalletIntentsImplementor {
VisaWalletIntents by visaWalletIntentsImplementor,
WalletPushPermissionClickIntents by pushPermissionClickIntentsImplementor {
override fun initialize(router: InnerWalletRouter, coroutineScope: CoroutineScope) {
super.initialize(router, coroutineScope)
@ -55,6 +57,7 @@ internal class WalletClickIntents @Inject constructor(
currencyActionsClickIntentsImplementor.initialize(router, coroutineScope)
contentClickIntentsImplementor.initialize(router, coroutineScope)
visaWalletIntentsImplementor.initialize(router, coroutineScope)
pushPermissionClickIntentsImplementor.initialize(router, coroutineScope)
}
fun onWalletChange(index: Int) {

View file

@ -44,6 +44,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
interface WalletCurrencyActionsClickIntents {
@ -58,6 +59,8 @@ interface WalletCurrencyActionsClickIntents {
fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onCopyAddressLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus): TextReference?
fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
@ -364,6 +367,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
showErrorIfDemoModeOrElse(action = ::openExplorer)
}
override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
// TODO staking
Timber.e("Not implemented yet")
}
private fun openExplorer() {
val userWalletId = stateHolder.getSelectedWalletId()
@ -466,6 +474,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private fun getUnavailabilityReasonText(unavailabilityReason: ScenarioUnavailabilityReason): TextReference {
return when (unavailabilityReason) {
is ScenarioUnavailabilityReason.StakingUnavailable -> {
resourceReference(
id = R.string.token_button_unavailability_reason_staking_unavailable,
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
)
}
is ScenarioUnavailabilityReason.PendingTransaction -> {
when (unavailabilityReason.withdrawalScenario) {
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(

View file

@ -0,0 +1,44 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
import com.tangem.domain.settings.DelayPermissionRequestUseCase
import com.tangem.domain.settings.NeverRequestPermissionUseCase
import com.tangem.domain.settings.SetFirstTimeAskingPermissionUseCase
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.launch
import javax.inject.Inject
internal interface WalletPushPermissionClickIntents {
fun onRequestPushPermission()
fun onDelayAskPushPermission()
fun onNeverAskPushPermission()
}
@ViewModelScoped
internal class WalletPushPermissionClickIntentsImplementor @Inject constructor(
private val setFirstTimeAskingPermissionUseCase: SetFirstTimeAskingPermissionUseCase,
private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase,
private val delayPermissionRequestUseCase: DelayPermissionRequestUseCase,
) : BaseWalletClickIntents(), WalletPushPermissionClickIntents {
override fun onRequestPushPermission() {
viewModelScope.launch {
setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION)
}
}
override fun onDelayAskPushPermission() {
viewModelScope.launch {
delayPermissionRequestUseCase(PUSH_PERMISSION)
}
}
override fun onNeverAskPushPermission() {
viewModelScope.launch {
neverRequestPermissionUseCase(PUSH_PERMISSION)
}
}
}

View file

@ -165,7 +165,10 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
override fun onScanToUnlockWalletClick() {
analyticsEventHandler.send(MainScreen.UnlockWithCardScan)
openScanCardDialog()
}
private fun openScanCardDialog() {
viewModelScope.launch(dispatchers.main) {
scanCardToUnlockWalletClickHandler(walletId = stateHolder.getSelectedWalletId())
.onLeft { error ->
@ -175,7 +178,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
event = WalletEvent.ShowAlert(WalletAlertState.WrongCardIsScanned),
)
}
ScanCardToUnlockWalletError.ManyScanFails -> router.openScanFailedDialog()
ScanCardToUnlockWalletError.ManyScanFails -> router.openScanFailedDialog(::openScanCardDialog)
}
}
}