Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-24 16:19:54 +08:00
parent dd9d7f2e45
commit 544c243291
16 changed files with 270 additions and 129 deletions

View file

@ -7,29 +7,19 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState import androidx.compose.material.SnackbarHostState
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.components.wallets.RenameWalletDialogContent
import com.tangem.core.ui.fragments.ComposeBottomSheetFragment import com.tangem.core.ui.fragments.ComposeBottomSheetFragment
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.analytics.events.MyWallets import com.tangem.tap.common.analytics.events.MyWallets
import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.details.ui.cardsettings.resolveReference
import com.tangem.tap.features.walletSelector.ui.components.BiometricsDisabledWarningContent import com.tangem.tap.features.walletSelector.ui.components.*
import com.tangem.tap.features.walletSelector.ui.components.BiometricsLockoutWarningContent
import com.tangem.tap.features.walletSelector.ui.components.KeyInvalidatedWarningContent
import com.tangem.tap.features.walletSelector.ui.components.RemoveWalletDialogContent
import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent
import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent
import com.tangem.tap.features.walletSelector.ui.model.DialogModel import com.tangem.tap.features.walletSelector.ui.model.DialogModel
import com.tangem.tap.features.walletSelector.ui.model.WarningModel import com.tangem.tap.features.walletSelector.ui.model.WarningModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -90,7 +80,13 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<Wa
if (dialog == null) return if (dialog == null) return
when (dialog) { when (dialog) {
is DialogModel.RemoveWalletDialog -> RemoveWalletDialogContent(dialog) is DialogModel.RemoveWalletDialog -> RemoveWalletDialogContent(dialog)
is DialogModel.RenameWalletDialog -> RenameWalletDialogContent(dialog) is DialogModel.RenameWalletDialog -> {
RenameWalletDialogContent(
name = dialog.currentName,
onConfirm = dialog.onConfirm,
onDismiss = dialog.onDismiss,
)
}
is WarningModel.BiometricsLockoutWarning -> BiometricsLockoutWarningContent(dialog) is WarningModel.BiometricsLockoutWarning -> BiometricsLockoutWarningContent(dialog)
is WarningModel.KeyInvalidatedWarning -> KeyInvalidatedWarningContent(dialog) is WarningModel.KeyInvalidatedWarning -> KeyInvalidatedWarningContent(dialog)
is WarningModel.BiometricsDisabledWarning -> BiometricsDisabledWarningContent(dialog) is WarningModel.BiometricsDisabledWarning -> BiometricsDisabledWarningContent(dialog)

View file

@ -1,69 +0,0 @@
package com.tangem.tap.features.walletSelector.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.AdditionalTextInputDialogParams
import com.tangem.core.ui.components.DialogButton
import com.tangem.core.ui.components.TextInputDialog
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
import com.tangem.wallet.R
@Composable
internal fun RenameWalletDialogContent(dialog: DialogModel.RenameWalletDialog) {
var value by remember {
mutableStateOf(TextFieldValue(text = dialog.currentName))
}
TextInputDialog(
fieldValue = value,
confirmButton = DialogButton(
title = stringResource(id = R.string.common_ok),
enabled = value.text.isNotEmpty() && value.text != dialog.currentName,
onClick = { dialog.onConfirm(value.text) },
),
onDismissDialog = dialog.onDismiss,
onValueChange = { newValue ->
value = newValue
},
title = stringResource(R.string.user_wallet_list_rename_popup_title),
dismissButton = DialogButton(
title = stringResource(id = R.string.common_cancel),
onClick = dialog.onDismiss,
),
textFieldParams = AdditionalTextInputDialogParams(
label = stringResource(R.string.user_wallet_list_rename_popup_placeholder),
),
)
}
// region Preview
@Composable
private fun RenameWalletDialogContentSample(modifier: Modifier = Modifier) {
Column(
modifier = modifier,
) {
RenameWalletDialogContent(dialog = DialogModel.RenameWalletDialog("", {}, {}))
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun RenameWalletDialogContentPreview_Light() {
TangemTheme {
RenameWalletDialogContentSample()
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun RenameWalletDialogContentPreview_Dark() {
TangemTheme(isDark = true) {
RenameWalletDialogContentSample()
}
}
// endregion Preview

View file

@ -0,0 +1,59 @@
package com.tangem.core.ui.components.wallets
import androidx.compose.runtime.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.AdditionalTextInputDialogParams
import com.tangem.core.ui.components.DialogButton
import com.tangem.core.ui.components.TextInputDialog
import com.tangem.core.ui.res.TangemTheme
/**
* Rename a wallet dialog
*
* @param name wallet name
* @param onConfirm lambda be invoked when Confirm button is clicked
* @param onDismiss lambda be invoked when dialog is dismissed
*/
@Composable
fun RenameWalletDialogContent(name: String, onConfirm: (newName: String) -> Unit, onDismiss: () -> Unit) {
var value by remember { mutableStateOf(TextFieldValue(text = name)) }
TextInputDialog(
fieldValue = value,
confirmButton = DialogButton(
title = stringResource(id = R.string.common_ok),
enabled = value.text.isNotEmpty() && value.text != name,
onClick = { onConfirm(value.text) },
),
onDismissDialog = onDismiss,
onValueChange = { value = it },
title = stringResource(R.string.user_wallet_list_rename_popup_title),
dismissButton = DialogButton(title = stringResource(id = R.string.common_cancel), onClick = onDismiss),
textFieldParams = AdditionalTextInputDialogParams(
label = stringResource(R.string.user_wallet_list_rename_popup_placeholder),
),
)
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun RenameWalletDialogContentPreview_Light() {
TangemTheme(isDark = false) {
RenameWalletDialogContent(name = "", onConfirm = {}, onDismiss = {})
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun RenameWalletDialogContentPreview_Dark() {
TangemTheme(isDark = true) {
RenameWalletDialogContent(name = "", onConfirm = {}, onDismiss = {})
}
}
// endregion Preview

View file

@ -35,7 +35,8 @@ internal object WalletPreviewData {
balance = "8923,05 $", balance = "8923,05 $",
additionalInfo = TextReference.Str("3 cards • Seed phrase"), additionalInfo = TextReference.Str("3 cards • Seed phrase"),
imageResId = R.drawable.ill_businessman_3d, imageResId = R.drawable.ill_businessman_3d,
onClick = null, onRenameClick = { _, _ -> },
onDeleteClick = {},
) )
} }
@ -44,7 +45,8 @@ internal object WalletPreviewData {
id = UserWalletId("321"), id = UserWalletId("321"),
title = "Wallet 1", title = "Wallet 1",
imageResId = R.drawable.ill_businessman_3d, imageResId = R.drawable.ill_businessman_3d,
onClick = null, onRenameClick = { _, _ -> },
onDeleteClick = {},
) )
} }
@ -53,7 +55,8 @@ internal object WalletPreviewData {
id = UserWalletId("42"), id = UserWalletId("42"),
title = "Wallet 1", title = "Wallet 1",
imageResId = R.drawable.ill_businessman_3d, imageResId = R.drawable.ill_businessman_3d,
onClick = null, onRenameClick = { _, _ -> },
onDeleteClick = {},
) )
} }
@ -62,7 +65,8 @@ internal object WalletPreviewData {
id = UserWalletId("24"), id = UserWalletId("24"),
title = "Wallet 1", title = "Wallet 1",
imageResId = R.drawable.ill_businessman_3d, imageResId = R.drawable.ill_businessman_3d,
onClick = null, onRenameClick = { _, _ -> },
onDeleteClick = {},
) )
} }

View file

@ -113,6 +113,10 @@ internal class DefaultWalletRouter(private val navigationStateHolder: Navigation
) )
} }
override fun openStoriesScreen() {
navigationStateHolder.navigate(action = NavigationAction.NavigateTo(screen = AppScreen.Home))
}
private companion object { private companion object {
const val BACKSTACK_ENTRY_COUNT_TO_CLOSE_WALLET_SCREEN = 2 const val BACKSTACK_ENTRY_COUNT_TO_CLOSE_WALLET_SCREEN = 2
} }

View file

@ -44,4 +44,7 @@ internal interface InnerWalletRouter : WalletRouter {
/** Open token details screen */ /** Open token details screen */
fun openTokenDetails(currency: CryptoCurrency) fun openTokenDetails(currency: CryptoCurrency)
/** Open stories screen */
fun openStoriesScreen()
} }

View file

@ -22,8 +22,11 @@ internal sealed interface WalletCardState {
@get:DrawableRes @get:DrawableRes
val imageResId: Int? val imageResId: Int?
/** Lambda be invoked when card is clicked */ /** Lambda be invoked when Rename button is clicked */
val onClick: (() -> Unit)? val onRenameClick: (UserWalletId, String) -> Unit
/** Lambda be invoked when Delete button is clicked */
val onDeleteClick: (UserWalletId) -> Unit
/** /**
* Wallet card content state * Wallet card content state
@ -32,7 +35,8 @@ internal sealed interface WalletCardState {
* @property title wallet name * @property title wallet name
* @property additionalInfo wallet additional info * @property additionalInfo wallet additional info
* @property imageResId wallet image resource id * @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked * @property onRenameClick lambda be invoked when Rename button is clicked
* @property onDeleteClick lambda be invoked when Delete button is clicked
* @property balance wallet balance * @property balance wallet balance
*/ */
data class Content( data class Content(
@ -40,7 +44,8 @@ internal sealed interface WalletCardState {
override val title: String, override val title: String,
override val additionalInfo: TextReference, override val additionalInfo: TextReference,
override val imageResId: Int?, override val imageResId: Int?,
override val onClick: (() -> Unit)? = null, override val onRenameClick: (UserWalletId, String) -> Unit,
override val onDeleteClick: (UserWalletId) -> Unit,
val balance: String, val balance: String,
) : WalletCardState ) : WalletCardState
@ -51,14 +56,16 @@ internal sealed interface WalletCardState {
* @property title wallet name * @property title wallet name
* @property additionalInfo wallet additional info * @property additionalInfo wallet additional info
* @property imageResId wallet image resource id * @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked * @property onRenameClick lambda be invoked when Rename button is clicked
* @property onDeleteClick lambda be invoked when Delete button is clicked
*/ */
data class HiddenContent( data class HiddenContent(
override val id: UserWalletId, override val id: UserWalletId,
override val title: String, override val title: String,
override val additionalInfo: TextReference = HIDDEN_BALANCE_TEXT, override val additionalInfo: TextReference = HIDDEN_BALANCE_TEXT,
override val imageResId: Int?, override val imageResId: Int?,
override val onClick: (() -> Unit)?, override val onRenameClick: (UserWalletId, String) -> Unit,
override val onDeleteClick: (UserWalletId) -> Unit,
) : WalletCardState ) : WalletCardState
/** /**
@ -68,14 +75,16 @@ internal sealed interface WalletCardState {
* @property title wallet name * @property title wallet name
* @property additionalInfo wallet additional info * @property additionalInfo wallet additional info
* @property imageResId wallet image resource id * @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked * @property onRenameClick lambda be invoked when Rename button is clicked
* @property onDeleteClick lambda be invoked when Delete button is clicked
*/ */
data class LockedContent( data class LockedContent(
override val id: UserWalletId, override val id: UserWalletId,
override val title: String, override val title: String,
override val additionalInfo: TextReference? = null, override val additionalInfo: TextReference? = null,
override val imageResId: Int?, override val imageResId: Int?,
override val onClick: (() -> Unit)?, override val onRenameClick: (UserWalletId, String) -> Unit,
override val onDeleteClick: (UserWalletId) -> Unit,
) : WalletCardState ) : WalletCardState
/** /**
@ -85,14 +94,16 @@ internal sealed interface WalletCardState {
* @property title wallet name * @property title wallet name
* @property additionalInfo wallet additional info * @property additionalInfo wallet additional info
* @property imageResId wallet image resource id * @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked * @property onRenameClick lambda be invoked when Rename button is clicked
* @property onDeleteClick lambda be invoked when Delete button is clicked
*/ */
data class Error( data class Error(
override val id: UserWalletId, override val id: UserWalletId,
override val title: String, override val title: String,
override val additionalInfo: TextReference = EMPTY_BALANCE_TEXT, override val additionalInfo: TextReference = EMPTY_BALANCE_TEXT,
override val imageResId: Int?, override val imageResId: Int?,
override val onClick: (() -> Unit)?, override val onRenameClick: (UserWalletId, String) -> Unit,
override val onDeleteClick: (UserWalletId) -> Unit,
) : WalletCardState ) : WalletCardState
/** /**
@ -102,16 +113,28 @@ internal sealed interface WalletCardState {
* @property title wallet name * @property title wallet name
* @property additionalInfo wallet additional info * @property additionalInfo wallet additional info
* @property imageResId wallet image resource id * @property imageResId wallet image resource id
* @property onClick lambda be invoked when wallet card is clicked * @property onRenameClick lambda be invoked when Rename button is clicked
* @property onDeleteClick lambda be invoked when Delete button is clicked
*/ */
data class Loading( data class Loading(
override val id: UserWalletId, override val id: UserWalletId,
override val title: String, override val title: String,
override val additionalInfo: TextReference? = null, override val additionalInfo: TextReference? = null,
override val imageResId: Int?, override val imageResId: Int?,
override val onClick: (() -> Unit)? = null, override val onRenameClick: (UserWalletId, String) -> Unit,
override val onDeleteClick: (UserWalletId) -> Unit,
) : WalletCardState ) : WalletCardState
fun copySealed(title: String = this.title): WalletCardState {
return when (this) {
is Content -> copy(title = title)
is Error -> copy(title = title)
is HiddenContent -> copy(title = title)
is Loading -> copy(title = title)
is LockedContent -> copy(title = title)
}
}
companion object { companion object {
val HIDDEN_BALANCE_TEXT by lazy { TextReference.Str(value = "•••") } val HIDDEN_BALANCE_TEXT by lazy { TextReference.Str(value = "•••") }
val EMPTY_BALANCE_TEXT by lazy { TextReference.Str(value = "") } val EMPTY_BALANCE_TEXT by lazy { TextReference.Str(value = "") }

View file

@ -31,7 +31,7 @@ internal class WalletLoadedTokensListConverter(
cardTypeResolverProvider: Provider<CardTypesResolver>, cardTypeResolverProvider: Provider<CardTypesResolver>,
currentWalletProvider: Provider<UserWallet>, currentWalletProvider: Provider<UserWallet>,
clickIntents: WalletClickIntents, clickIntents: WalletClickIntents,
) : Converter<LoadedTokensListModel, WalletMultiCurrencyState.Content> { ) : Converter<LoadedTokensListModel, WalletState> {
private val tokenListStateConverter = TokenListToWalletStateConverter( private val tokenListStateConverter = TokenListToWalletStateConverter(
currentStateProvider = currentStateProvider, currentStateProvider = currentStateProvider,
@ -46,7 +46,7 @@ internal class WalletLoadedTokensListConverter(
currentStateProvider = currentStateProvider, currentStateProvider = currentStateProvider,
) )
override fun convert(value: LoadedTokensListModel): WalletMultiCurrencyState.Content { override fun convert(value: LoadedTokensListModel): WalletState {
return value.tokenListEither.fold( return value.tokenListEither.fold(
ifLeft = tokenListErrorStateConverter::convert, ifLeft = tokenListErrorStateConverter::convert,
ifRight = { ifRight = {

View file

@ -67,6 +67,8 @@ internal class WalletRefreshStateConverter(
title = selectedWallet.title, title = selectedWallet.title,
additionalInfo = additionalInfo, additionalInfo = additionalInfo,
imageResId = selectedWallet.imageResId, imageResId = selectedWallet.imageResId,
onRenameClick = selectedWallet.onRenameClick,
onDeleteClick = selectedWallet.onDeleteClick,
), ),
), ),
) )

View file

@ -95,7 +95,8 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
currencyAmount = status.amount, currencyAmount = status.amount,
), ),
imageResId = selectedWallet.imageResId, imageResId = selectedWallet.imageResId,
onClick = selectedWallet.onClick, onRenameClick = selectedWallet.onRenameClick,
onDeleteClick = selectedWallet.onDeleteClick,
balance = formatFiatAmount(status = status, appCurrency = appCurrencyProvider()), balance = formatFiatAmount(status = status, appCurrency = appCurrencyProvider()),
) )
} }
@ -104,7 +105,8 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
id = selectedWallet.id, id = selectedWallet.id,
title = selectedWallet.title, title = selectedWallet.title,
imageResId = selectedWallet.imageResId, imageResId = selectedWallet.imageResId,
onClick = selectedWallet.onClick, onRenameClick = selectedWallet.onRenameClick,
onDeleteClick = selectedWallet.onDeleteClick,
) )
} }
is CryptoCurrencyStatus.MissedDerivation, is CryptoCurrencyStatus.MissedDerivation,
@ -116,7 +118,8 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
id = selectedWallet.id, id = selectedWallet.id,
title = selectedWallet.title, title = selectedWallet.title,
imageResId = selectedWallet.imageResId, imageResId = selectedWallet.imageResId,
onClick = selectedWallet.onClick, onRenameClick = selectedWallet.onRenameClick,
onDeleteClick = selectedWallet.onDeleteClick,
) )
} }
} }

View file

@ -97,7 +97,7 @@ internal class WalletSkeletonStateConverter(
// If wallet is initialized, return it, otherwise return loading state // If wallet is initialized, return it, otherwise return loading state
if (initializedWallet !is WalletCardState.Loading) { if (initializedWallet !is WalletCardState.Loading) {
initializedWallet initializedWallet.copySealed(title = wallet.name)
} else { } else {
createWalletLoadingState(wallet) createWalletLoadingState(wallet)
} }
@ -118,6 +118,8 @@ internal class WalletSkeletonStateConverter(
null null
}, },
imageResId = WalletImageResolver.resolve(cardTypesResolver = cardTypeResolver), imageResId = WalletImageResolver.resolve(cardTypesResolver = cardTypeResolver),
onRenameClick = clickIntents::onRenameClick,
onDeleteClick = clickIntents::onDeleteClick,
) )
} }

View file

@ -200,11 +200,12 @@ internal class WalletStateFactory(
id = walletCardState.id, id = walletCardState.id,
title = walletCardState.title, title = walletCardState.title,
imageResId = walletCardState.imageResId, imageResId = walletCardState.imageResId,
onClick = walletCardState.onClick,
additionalInfo = WalletAdditionalInfoFactory.resolve( additionalInfo = WalletAdditionalInfoFactory.resolve(
cardTypesResolver = cardTypeResolver, cardTypesResolver = cardTypeResolver,
wallet = currentWalletProvider(), wallet = currentWalletProvider(),
), ),
onRenameClick = walletCardState.onRenameClick,
onDeleteClick = walletCardState.onDeleteClick,
) )
} }
.toImmutableList(), .toImmutableList(),
@ -227,7 +228,8 @@ internal class WalletStateFactory(
id = walletCardState.id, id = walletCardState.id,
title = walletCardState.title, title = walletCardState.title,
imageResId = walletCardState.imageResId, imageResId = walletCardState.imageResId,
onClick = walletCardState.onClick, onRenameClick = walletCardState.onRenameClick,
onDeleteClick = walletCardState.onDeleteClick,
) )
} }
.toImmutableList(), .toImmutableList(),

View file

@ -1,23 +1,38 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.*
import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon import androidx.compose.material.icons.Icons
import androidx.compose.material3.Surface import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.Text import androidx.compose.material.icons.outlined.Edit
import androidx.compose.runtime.Composable import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
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
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.ConstraintLayoutScope import androidx.constraintlayout.compose.ConstraintLayoutScope
@ -25,8 +40,10 @@ import androidx.constraintlayout.compose.Dimension
import com.tangem.core.ui.components.FontSizeRange import com.tangem.core.ui.components.FontSizeRange
import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.ResizableText import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.wallets.RenameWalletDialogContent
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemDimens import com.tangem.core.ui.res.TangemDimens
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.impl.R
@ -44,7 +61,12 @@ import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCard
@Composable @Composable
internal fun WalletCard(state: WalletCardState, modifier: Modifier = Modifier) { internal fun WalletCard(state: WalletCardState, modifier: Modifier = Modifier) {
@Suppress("DestructuringDeclarationWithTooManyEntries") @Suppress("DestructuringDeclarationWithTooManyEntries")
CardContainer(onClick = state.onClick, modifier = modifier) { CardContainer(
name = state.title,
onDeleteClick = { state.onDeleteClick(state.id) },
onRenameClick = { state.onRenameClick(state.id, it) },
modifier = modifier,
) {
val (title, balance, additionalText, image) = createRefs() val (title, balance, additionalText, image) = createRefs()
val contentVerticalMargin = TangemTheme.dimens.spacing12 val contentVerticalMargin = TangemTheme.dimens.spacing12
@ -92,16 +114,43 @@ internal fun WalletCard(state: WalletCardState, modifier: Modifier = Modifier) {
@Composable @Composable
private fun CardContainer( private fun CardContainer(
onClick: (() -> Unit)?, name: String,
onDeleteClick: () -> Unit,
onRenameClick: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
content: @Composable ConstraintLayoutScope.() -> Unit, content: @Composable (ConstraintLayoutScope.() -> Unit),
) { ) {
var isMenuVisible by rememberSaveable { mutableStateOf(value = false) }
var pressOffset by remember { mutableStateOf(value = DpOffset.Zero) }
var itemHeight by remember { mutableStateOf(value = 0.dp) }
val density = LocalDensity.current
val interactionSource = remember { MutableInteractionSource() }
val haptic = LocalHapticFeedback.current
Surface( Surface(
modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size108), modifier = modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size108)
.onSizeChanged { itemHeight = with(density) { it.height.toDp() } }
.clip(shape = TangemTheme.shapes.roundedCornersXMedium)
.indication(interactionSource = interactionSource, indication = LocalIndication.current)
.pointerInput(true) {
detectTapGestures(
onLongPress = {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
isMenuVisible = true
pressOffset = DpOffset(x = it.x.toDp(), y = it.y.toDp())
},
onPress = {
val press = PressInteraction.Press(it)
interactionSource.emit(press)
tryAwaitRelease()
interactionSource.emit(PressInteraction.Release(press))
},
)
},
shape = TangemTheme.shapes.roundedCornersXMedium, shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.background.primary, color = TangemTheme.colors.background.primary,
onClick = onClick ?: {},
enabled = onClick != null,
) { ) {
ConstraintLayout( ConstraintLayout(
modifier = Modifier modifier = Modifier
@ -111,6 +160,50 @@ private fun CardContainer(
content() content()
} }
} }
var isRenameWalletDialogVisible by rememberSaveable { mutableStateOf(value = false) }
DropdownMenu(
expanded = isMenuVisible,
onDismissRequest = { isMenuVisible = false },
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
offset = pressOffset.copy(y = pressOffset.y - itemHeight),
) {
MenuItem(
textResId = R.string.common_rename,
imageVector = Icons.Outlined.Edit,
onClick = {
isMenuVisible = false
isRenameWalletDialogVisible = true
},
)
MenuItem(textResId = R.string.common_delete, imageVector = Icons.Outlined.Delete, onClick = onDeleteClick)
}
if (isRenameWalletDialogVisible) {
RenameWalletDialogContent(
name = name,
onConfirm = {
onRenameClick(it)
isRenameWalletDialogVisible = false
},
onDismiss = { isRenameWalletDialogVisible = false },
)
}
}
@Composable
private fun MenuItem(@StringRes textResId: Int, imageVector: ImageVector, onClick: () -> Unit) {
DropdownMenuItem(
text = { Text(text = stringResource(id = textResId), style = TangemTheme.typography.subtitle2) },
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
trailingIcon = { Icon(imageVector = imageVector, contentDescription = null) },
onClick = onClick,
colors = MenuDefaults.itemColors(
textColor = TangemTheme.colors.text.primary1,
trailingIconColor = TangemColorPalette.Dark6,
),
)
} }
@Composable @Composable

View file

@ -27,7 +27,7 @@ internal class FiatBalanceToWalletCardConverter(
} }
private fun WalletCardState.toLoadingWalletCardState(): WalletCardState { private fun WalletCardState.toLoadingWalletCardState(): WalletCardState {
return WalletCardState.Loading(id, title, additionalInfo, imageResId, onClick) return WalletCardState.Loading(id, title, additionalInfo, imageResId, onRenameClick, onDeleteClick)
} }
private fun WalletCardState.toErrorWalletCardState(): WalletCardState { private fun WalletCardState.toErrorWalletCardState(): WalletCardState {
@ -35,7 +35,8 @@ internal class FiatBalanceToWalletCardConverter(
id = id, id = id,
title = title, title = title,
imageResId = imageResId, imageResId = imageResId,
onClick = onClick, onDeleteClick = onDeleteClick,
onRenameClick = onRenameClick,
additionalInfo = WalletAdditionalInfoFactory.resolve( additionalInfo = WalletAdditionalInfoFactory.resolve(
cardTypesResolver = cardTypeResolverProvider(), cardTypesResolver = cardTypeResolverProvider(),
wallet = currentWalletProvider(), wallet = currentWalletProvider(),
@ -50,7 +51,8 @@ internal class FiatBalanceToWalletCardConverter(
title = currentState.title, title = currentState.title,
additionalInfo = currentState.additionalInfo ?: WalletCardState.HIDDEN_BALANCE_TEXT, additionalInfo = currentState.additionalInfo ?: WalletCardState.HIDDEN_BALANCE_TEXT,
imageResId = currentState.imageResId, imageResId = currentState.imageResId,
onClick = currentState.onClick, onRenameClick = currentState.onRenameClick,
onDeleteClick = currentState.onDeleteClick,
) )
} else { } else {
val appCurrency = appCurrencyProvider() val appCurrency = appCurrencyProvider()
@ -63,7 +65,8 @@ internal class FiatBalanceToWalletCardConverter(
wallet = currentWalletProvider(), wallet = currentWalletProvider(),
), ),
imageResId = currentState.imageResId, imageResId = currentState.imageResId,
onClick = currentState.onClick, onRenameClick = currentState.onRenameClick,
onDeleteClick = currentState.onDeleteClick,
balance = formatFiatAmount( balance = formatFiatAmount(
fiatAmount = this.amount, fiatAmount = this.amount,
fiatCurrencyCode = appCurrency.code, fiatCurrencyCode = appCurrency.code,

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.utils
import com.tangem.common.Provider import com.tangem.common.Provider
import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import com.tangem.utils.converter.Converter import com.tangem.utils.converter.Converter
@ -10,11 +11,23 @@ import kotlinx.collections.immutable.persistentListOf
internal class TokenListErrorConverter( internal class TokenListErrorConverter(
private val currentStateProvider: Provider<WalletState>, private val currentStateProvider: Provider<WalletState>,
) : Converter<TokenListError, WalletMultiCurrencyState.Content> { ) : Converter<TokenListError, WalletState> {
override fun convert(value: TokenListError): WalletMultiCurrencyState.Content { override fun convert(value: TokenListError): WalletState {
return requireNotNull(currentStateProvider() as? WalletMultiCurrencyState.Content).copy( return when (val state = currentStateProvider()) {
tokensListState = WalletTokensListState.Content(items = persistentListOf(), onOrganizeTokensClick = null), is WalletMultiCurrencyState.Content -> {
) state.copy(
tokensListState = WalletTokensListState.Content(
items = persistentListOf(),
onOrganizeTokensClick = null,
),
)
}
is WalletMultiCurrencyState.Locked,
is WalletSingleCurrencyState.Content,
is WalletSingleCurrencyState.Locked,
is WalletState.Initial,
-> state
}
} }
} }

View file

@ -471,7 +471,10 @@ internal class WalletViewModel @Inject constructor(
override fun onDeleteClick(userWalletId: UserWalletId) { override fun onDeleteClick(userWalletId: UserWalletId) {
viewModelScope.launch(dispatchers.io) { viewModelScope.launch(dispatchers.io) {
deleteWalletUseCase(userWalletId) val either = deleteWalletUseCase(userWalletId)
val state = requireNotNull(uiState as? WalletState.ContentState)
if (state.walletsListConfig.wallets.size <= 1 && either.isRight()) router.openStoriesScreen()
} }
} }