Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-12 00:36:45 +04:00
parent 0cc270adc0
commit 47412b1d63
28 changed files with 1110 additions and 2 deletions

View file

@ -29,6 +29,7 @@ import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment
import com.tangem.tap.features.tokens.ui.AddTokensFragment
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.features.walletSelector.ui.WalletSelectorBottomSheetFragment
import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.wallet.R
import timber.log.Timber
@ -52,10 +53,14 @@ fun FragmentActivity.openFragment(
}
if (screen.isDialogFragment) {
(fragment as DialogFragment).show(transaction, screen.name)
if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null)
if (addToBackstack) {
transaction.addToBackStack(null)
}
} else {
transaction.replace(R.id.fragment_container, fragment, screen.name)
if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null)
if (addToBackstack && (screen != AppScreen.Home && screen != AppScreen.Welcome)) {
transaction.addToBackStack(null)
}
transaction.commitAllowingStateLoss()
}
}
@ -114,5 +119,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.ReferralProgram -> ReferralFragment()
AppScreen.Welcome -> WelcomeFragment()
AppScreen.SaveWallet -> SaveWalletBottomSheetFragment()
AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment()
}
}

View file

@ -35,6 +35,7 @@ import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
import com.tangem.tap.features.welcome.redux.WelcomeState
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
import org.rekotlin.Middleware
import org.rekotlin.StateType
@ -55,6 +56,7 @@ data class AppState(
val shopState: ShopState = ShopState(),
val welcomeState: WelcomeState = WelcomeState(),
val saveWalletState: SaveWalletState = SaveWalletState(),
val walletSelectorState: WalletSelectorState = WalletSelectorState(),
) : StateType {
val domainState: DomainState

View file

@ -25,4 +25,5 @@ enum class AppScreen(
ReferralProgram,
Welcome,
SaveWallet(isDialogFragment = true),
WalletSelector(isDialogFragment = true),
}

View file

@ -0,0 +1,31 @@
package com.tangem.tap.features.walletSelector.redux
import com.tangem.common.core.TangemError
import org.rekotlin.Action
internal sealed interface WalletSelectorAction : Action {
object UnlockWithBiometry : WalletSelectorAction {
object Success : WalletSelectorAction
data class Error(val error: TangemError) : WalletSelectorAction
}
data class SelectWallet(
val walletId: String,
) : WalletSelectorAction
data class RenameWallet(
val walletId: String,
val newName: String,
) : WalletSelectorAction
data class RemoveWallets(
val walletIdsToRemove: List<String>,
) : WalletSelectorAction
object AddWallet : WalletSelectorAction {
object Success : WalletSelectorAction
data class Error(val error: TangemError) : WalletSelectorAction
}
object CloseError : WalletSelectorAction
}

View file

@ -0,0 +1,5 @@
package com.tangem.tap.features.walletSelector.redux
import org.rekotlin.StateType
class WalletSelectorState : StateType

View file

@ -0,0 +1,80 @@
package com.tangem.tap.features.walletSelector.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.runtime.Composable
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.Modifier
import androidx.fragment.app.viewModels
import com.tangem.core.ui.fragments.ComposeBottomSheetFragment
import com.tangem.core.ui.res.TangemTheme
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.RenameWalletDialog
internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<WalletSelectorScreenState>() {
private val viewModel by viewModels<WalletSelectorViewModel>()
@Composable
override fun provideState(): State<WalletSelectorScreenState> {
return viewModel.state.collectAsState()
}
@Composable
override fun ScreenContent(
modifier: Modifier,
state: WalletSelectorScreenState,
) {
val snackbarHostState = remember { SnackbarHostState() }
val errorMessage by rememberUpdatedState(newValue = state.error?.customMessage)
val renameWalletDialog by rememberUpdatedState(newValue = state.renameWalletDialog)
Box(modifier = modifier) {
WalletSelectorScreenContent(
state = state,
onWalletClick = viewModel::walletClicked,
onWalletLongClick = viewModel::walletLongClicked,
onUnlockClick = viewModel::unlock,
onAddCardClick = viewModel::addWallet,
onClearSelectedClick = viewModel::cancelWalletsEditing,
onEditSelectedWalletClick = viewModel::renameWallet,
onDeleteSelectedWalletsClick = viewModel::deleteWallets,
)
SnackbarHost(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(vertical = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
hostState = snackbarHostState,
)
}
RenameWalletDialog(dialog = renameWalletDialog)
LaunchedEffect(key1 = errorMessage) {
errorMessage?.let {
snackbarHostState.showSnackbar(it)
viewModel.closeError()
}
}
}
@Composable
private fun RenameWalletDialog(
modifier: Modifier = Modifier,
dialog: RenameWalletDialog?,
) {
if (dialog == null) return
RenameWalletDialogContent(modifier, dialog)
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.tap.features.walletSelector.ui
import androidx.compose.runtime.Immutable
import com.tangem.common.core.TangemError
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog
import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem
@Immutable
internal data class WalletSelectorScreenState(
val wallets: List<UserWalletItem> = listOf(),
val selectedWalletId: String? = null,
val fiatCurrency: FiatCurrency = FiatCurrency.Default,
val isLocked: Boolean = true,
val editingWalletsIds: List<String> = listOf(),
val renameWalletDialog: RenameWalletDialog? = null,
val showAddCardProgress: Boolean = false,
val showUnlockProgress: Boolean = false,
val error: TangemError? = null,
)

View file

@ -0,0 +1,143 @@
package com.tangem.tap.features.walletSelector.ui
import androidx.lifecycle.ViewModel
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog
import com.tangem.tap.store
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import org.rekotlin.StoreSubscriber
internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSelectorState> {
private val stateInternal = MutableStateFlow(WalletSelectorScreenState())
val state: StateFlow<WalletSelectorScreenState> = stateInternal
init {
subscribeToStoreChanges()
bootstrapWalletListChanges()
bootstrapWalletsStoresChanges()
}
fun unlock() {
store.dispatch(WalletSelectorAction.UnlockWithBiometry)
}
fun addWallet() {
store.dispatch(WalletSelectorAction.AddWallet)
}
fun walletClicked(walletId: String) = with(state.value) {
when {
isLocked -> Unit
editingWalletsIds.isNotEmpty() && !editingWalletsIds.contains(walletId) -> {
editWallet(walletId)
}
editingWalletsIds.isNotEmpty() && editingWalletsIds.contains(walletId) -> {
cancelWalletEditing(walletId)
}
selectedWalletId != walletId -> {
store.dispatch(WalletSelectorAction.SelectWallet(walletId))
}
}
}
fun walletLongClicked(walletId: String) = with(state.value) {
if (!isLocked && editingWalletsIds.isEmpty()) {
editWallet(walletId)
}
}
fun cancelWalletsEditing() {
stateInternal.update { prevState ->
prevState.copy(
editingWalletsIds = emptyList(),
)
}
}
fun renameWallet() = with(state.value) {
if (editingWalletsIds.isNotEmpty() && renameWalletDialog == null) {
val editedWalletId = editingWalletsIds.first()
val editedWallet = wallets.find { it.id == editedWalletId }
if (editedWallet != null) {
val dialog = RenameWalletDialog(
currentName = editedWallet.name,
onApply = { newName ->
store.dispatch(WalletSelectorAction.RenameWallet(editedWalletId, newName))
stateInternal.update { prevState ->
prevState.copy(
renameWalletDialog = null,
editingWalletsIds = emptyList(),
)
}
},
onCancel = {
stateInternal.update { prevState ->
prevState.copy(
renameWalletDialog = null,
)
}
},
)
stateInternal.update { prevState ->
prevState.copy(
renameWalletDialog = dialog,
)
}
}
}
}
fun deleteWallets() = with(state.value) {
if (editingWalletsIds.isNotEmpty()) {
store.dispatch(WalletSelectorAction.RemoveWallets(walletIdsToRemove = editingWalletsIds))
}
}
fun closeError() {
store.dispatch(WalletSelectorAction.CloseError)
}
override fun newState(state: WalletSelectorState) {
// TODO: Update UI state
}
override fun onCleared() {
store.unsubscribe(this)
}
private fun editWallet(walletId: String) {
stateInternal.update { prevState ->
prevState.copy(
editingWalletsIds = prevState.editingWalletsIds + walletId,
)
}
}
private fun cancelWalletEditing(walletId: String) {
stateInternal.update { prevState ->
prevState.copy(
editingWalletsIds = prevState.editingWalletsIds - walletId,
)
}
}
private fun subscribeToStoreChanges() {
store.subscribe(this) { appState ->
appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState }
.select { it.walletSelectorState }
}
}
private fun bootstrapWalletListChanges() {
// TODO
}
private fun bootstrapWalletsStoresChanges() {
// TODO
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.tap.features.walletSelector.ui.components
import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState
import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem
internal object MockData {
private val userWallet = UserWalletItem(
id = "wallet_1",
balance = UserWalletItem.Balance(
amount = "6781.05 $",
isLoading = false,
),
name = "Wallet",
imageUrl = "https://app.tangem.com/cards/card_default.png",
type = UserWalletItem.Type.MultiCurrency(tokensCount = 12, cardsInWallet = 3),
)
val userWalletList = listOf(
userWallet,
userWallet.copy(id = "wallet_2"),
userWallet.copy(id = "wallet_3", type = UserWalletItem.Type.MultiCurrency(tokensCount = 2, cardsInWallet = 1)),
userWallet.copy(id = "wallet_4", type = UserWalletItem.Type.SingleCurrency(tokenName = "Ethereum")),
)
val state = WalletSelectorScreenState(
wallets = userWalletList,
selectedWalletId = userWalletList[0].id,
isLocked = false,
)
}

View file

@ -0,0 +1,111 @@
package com.tangem.tap.features.walletSelector.ui.components
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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 androidx.compose.ui.window.Dialog
import com.tangem.core.ui.components.OutlineTextField
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.TextButton
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog
import com.tangem.wallet.R
@Composable
internal fun RenameWalletDialogContent(
modifier: Modifier = Modifier,
dialog: RenameWalletDialog,
) {
Dialog(onDismissRequest = dialog.onCancel) {
Column(
modifier = modifier
.background(
shape = TangemTheme.shapes.roundedCornersLarge,
color = TangemTheme.colors.background.plain,
)
.padding(all = TangemTheme.dimens.spacing24),
) {
var value by remember {
mutableStateOf(TextFieldValue(text = dialog.currentName))
}
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.user_wallet_list_rename_popup_title),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
SpacerH16()
OutlineTextField(
value = value,
label = stringResource(R.string.user_wallet_list_rename_popup_placeholder),
onValueChange = { newValue ->
value = newValue
},
)
SpacerH24()
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
TextButton(
text = stringResource(id = R.string.common_cancel),
onClick = dialog.onCancel,
)
SpacerW8()
TextButton(
text = stringResource(id = R.string.common_save),
enabled = value.text.isNotEmpty() && value.text != dialog.currentName,
onClick = {
dialog.onApply(value.text)
},
)
}
}
}
}
// region Preview
@Composable
private fun RenameWalletDialogContentSample(
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.background(TangemTheme.colors.background.primary),
) {
RenameWalletDialogContent(dialog = 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,251 @@
package com.tangem.tap.features.walletSelector.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButtonIconRight
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.SpacerW16
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState
import com.tangem.wallet.R
@Composable
internal fun WalletSelectorScreenContent(
modifier: Modifier = Modifier,
state: WalletSelectorScreenState,
onWalletClick: (walletId: String) -> Unit,
onWalletLongClick: (walletId: String) -> Unit,
onUnlockClick: () -> Unit,
onAddCardClick: () -> Unit,
onClearSelectedClick: () -> Unit,
onEditSelectedWalletClick: () -> Unit,
onDeleteSelectedWalletsClick: () -> Unit,
) {
val editingWalletsSize by rememberUpdatedState(newValue = state.editingWalletsIds.size)
Column(modifier = modifier) {
Hand()
SpacerH12()
if (editingWalletsSize > 0) {
EditWalletsBar(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
editingWalletsSize = editingWalletsSize,
onClearSelectedClick = onClearSelectedClick,
onEditSelectedWalletClick = onEditSelectedWalletClick,
onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick,
)
} else {
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.user_wallet_list_title),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
}
SpacerH12()
WalletList(
wallets = state.wallets,
selectedWalletId = state.selectedWalletId,
checkedWalletIds = state.editingWalletsIds,
isLocked = state.isLocked,
onWalletClick = onWalletClick,
onWalletLongClick = onWalletLongClick,
)
SpacerH24()
if (state.isLocked) {
PrimaryButton(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
text = stringResource(R.string.user_wallet_list_unlock_all_face_id),
showProgress = state.showUnlockProgress,
onClick = onUnlockClick,
)
SpacerH12()
}
SecondaryButtonIconRight(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
text = stringResource(R.string.user_wallet_list_add_button),
showProgress = state.showAddCardProgress,
icon = painterResource(id = R.drawable.ic_tangem),
onClick = onAddCardClick,
)
SpacerH16()
}
}
@Composable
private fun EditWalletsBar(
modifier: Modifier = Modifier,
editingWalletsSize: Int,
onClearSelectedClick: () -> Unit,
onEditSelectedWalletClick: () -> Unit,
onDeleteSelectedWalletsClick: () -> Unit,
) {
val showEditAction = remember(editingWalletsSize) { editingWalletsSize in 1 until 2 }
Row(
modifier = modifier
.padding(vertical = TangemTheme.dimens.spacing8)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
IconButton(
modifier = Modifier.size(TangemTheme.dimens.size32),
onClick = onClearSelectedClick,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = R.drawable.ic_close),
tint = TangemTheme.colors.icon.primary1,
contentDescription = "Unselect wallets",
)
}
SpacerW16()
Text(
text = stringResource(id = R.string.user_wallet_list_editing_count, editingWalletsSize),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
SpacerWMax()
if (showEditAction) {
IconButton(
modifier = Modifier.size(TangemTheme.dimens.size32),
onClick = onEditSelectedWalletClick,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = R.drawable.ic_pencil_outline_24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = "Edit wallet name",
)
}
SpacerW8()
}
IconButton(
modifier = Modifier.size(TangemTheme.dimens.size32),
onClick = onDeleteSelectedWalletsClick,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = R.drawable.ic_trash),
tint = TangemTheme.colors.icon.warning,
contentDescription = "Delete selected wallets",
)
}
}
}
// region Preview
@Composable
private fun WalletSelectorScreenContentSample(
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.background(color = TangemTheme.colors.background.primary),
) {
WalletSelectorScreenContent(
modifier = Modifier
.background(
color = TangemTheme.colors.background.plain,
shape = TangemTheme.shapes.bottomSheet,
),
state = MockData.state.copy(isLocked = true),
onWalletClick = { /* no-op */ },
onWalletLongClick = { /* no-op */ },
onUnlockClick = { /* no-op */ },
onAddCardClick = { /* no-op */ },
onEditSelectedWalletClick = { /* no-op */ },
onClearSelectedClick = { /* no-op */ },
onDeleteSelectedWalletsClick = { /* no-op */ },
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun WalletSelectorScreenContentPreview_Light() {
TangemTheme {
WalletSelectorScreenContentSample()
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun WalletSelectorScreenContentPreview_Dark() {
TangemTheme(isDark = true) {
WalletSelectorScreenContentSample()
}
}
@Composable
private fun WalletSelectorScreenContent_EditWallets_Sample(
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.background(TangemTheme.colors.background.primary),
) {
WalletSelectorScreenContent(
modifier = Modifier
.background(
color = TangemTheme.colors.background.plain,
shape = TangemTheme.shapes.bottomSheet,
),
state = MockData.state
.copy(editingWalletsIds = listOf(MockData.userWalletList[2].id)),
onWalletClick = { /* no-op */ },
onWalletLongClick = { /* no-op */ },
onUnlockClick = { /* no-op */ },
onAddCardClick = { /* no-op */ },
onEditSelectedWalletClick = { /* no-op */ },
onClearSelectedClick = { /* no-op */ },
onDeleteSelectedWalletsClick = { /* no-op */ },
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun WalletSelectorScreenContent_EditWallets_Preview_Light() {
TangemTheme {
WalletSelectorScreenContent_EditWallets_Sample()
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun WalletSelectorScreenContent_EditWallets_Preview_Dark() {
TangemTheme(isDark = true) {
WalletSelectorScreenContent_EditWallets_Sample()
}
}
// endregion Preview

View file

@ -0,0 +1,204 @@
@file:OptIn(ExperimentalComposeUiApi::class)
package com.tangem.tap.features.walletSelector.ui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.SpacerH2
import com.tangem.core.ui.components.SpacerW6
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.compose.TangemTypography
import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem
import com.tangem.wallet.R
@Composable
internal fun WalletItem(
modifier: Modifier = Modifier,
wallet: UserWalletItem,
isSelected: Boolean,
isChecked: Boolean,
isLocked: Boolean,
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.width(TangemTheme.dimens.size62)
.height(TangemTheme.dimens.size42),
) {
val cardImageModifier = Modifier
.align(Alignment.Center)
.width(TangemTheme.dimens.size56)
.height(TangemTheme.dimens.size32)
.clip(TangemTheme.shapes.roundedCornersSmall)
SubcomposeAsyncImage(
modifier = cardImageModifier,
model = ImageRequest.Builder(LocalContext.current)
.data(wallet.imageUrl)
.crossfade(true)
.build(),
loading = { CardImagePlaceholder(modifier = cardImageModifier) },
error = { CardImagePlaceholder(modifier = cardImageModifier) },
contentDescription = null,
)
when {
isChecked -> {
Box(
modifier = cardImageModifier
.background(
color = TangemTheme.colors.icon.accent.copy(alpha = .5f),
),
contentAlignment = Alignment.Center,
) {
Icon(
painter = painterResource(id = R.drawable.ic_check_24),
tint = TangemTheme.colors.icon.primary2,
contentDescription = null,
)
}
}
isSelected -> {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(TangemTheme.dimens.size18)
.background(
color = Color.White,
shape = CircleShape,
),
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier
.padding(all = (0.5).dp),
painter = painterResource(id = R.drawable.ic_check_circle_18),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
}
}
else -> Unit
}
}
SpacerW8()
Column(
modifier = Modifier.weight(weight = .6f),
verticalArrangement = Arrangement.SpaceAround,
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = wallet.name,
color = if (isSelected) TangemTheme.colors.text.accent else TangemTheme.colors.text.primary1,
style = TangemTypography.subtitle1,
)
SpacerH2()
Text(
modifier = Modifier.fillMaxWidth(),
text = when (val type = wallet.type) {
is UserWalletItem.Type.MultiCurrency -> pluralStringResource(
id = R.plurals.card_label_card_count,
count = type.cardsInWallet,
type.cardsInWallet,
)
is UserWalletItem.Type.SingleCurrency -> type.tokenName
},
style = TangemTheme.typography.caption,
color = TangemTheme.colors.text.tertiary,
)
}
SpacerW6()
if (isLocked) {
Box(
modifier = Modifier
.padding(vertical = TangemTheme.dimens.spacing2)
.background(
color = TangemTheme.colors.button.secondary,
shape = TangemTheme.shapes.roundedCornersMedium,
),
) {
Icon(
modifier = Modifier
.padding(
vertical = TangemTheme.dimens.spacing8,
horizontal = TangemTheme.dimens.spacing12,
)
.size(TangemTheme.dimens.size20),
painter = painterResource(id = R.drawable.ic_locked_24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
)
}
} else {
Column(
modifier = Modifier.weight(weight = .4f),
verticalArrangement = Arrangement.SpaceAround,
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = wallet.balance.amount,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.End,
)
when (val type = wallet.type) {
is UserWalletItem.Type.MultiCurrency -> {
SpacerH2()
Text(
modifier = Modifier.fillMaxWidth(),
text = pluralStringResource(
id = R.plurals.tokens_count,
count = type.tokensCount,
type.tokensCount,
),
style = TangemTheme.typography.caption,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.End,
)
}
is UserWalletItem.Type.SingleCurrency -> Unit
}
}
}
}
}
@Composable
private fun CardImagePlaceholder(
modifier: Modifier = Modifier,
) {
Image(
modifier = modifier,
painter = painterResource(id = R.drawable.card_placeholder_black),
contentDescription = null,
)
}

View file

@ -0,0 +1,66 @@
@file:OptIn(ExperimentalFoundationApi::class)
package com.tangem.tap.features.walletSelector.ui.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem
@Composable
internal fun WalletList(
modifier: Modifier = Modifier,
wallets: List<UserWalletItem>,
selectedWalletId: String?,
checkedWalletIds: List<String>,
isLocked: Boolean,
onWalletClick: (walletId: String) -> Unit,
onWalletLongClick: (walletId: String) -> Unit,
) {
val multiCurrencyWallets = remember(wallets) {
wallets.filter { it.type is UserWalletItem.Type.MultiCurrency }
}
val singleCurrencyWallets = remember(wallets) {
wallets.filter { it.type is UserWalletItem.Type.SingleCurrency }
}
LazyColumn(modifier = modifier) {
val walletSection: (List<UserWalletItem>) -> Unit = { wallets ->
itemsIndexed(
items = wallets,
key = { _, item -> item.id },
) { index, wallet ->
if (index == 0) {
Text(
modifier = Modifier.padding(start = TangemTheme.dimens.spacing16),
text = wallet.type.headerText.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
}
WalletItem(
modifier = Modifier
.combinedClickable(
onClick = { onWalletClick(wallet.id) },
onLongClick = { onWalletLongClick(wallet.id) },
)
.padding(all = TangemTheme.dimens.spacing16),
wallet = wallet,
isSelected = wallet.id == selectedWalletId,
isChecked = wallet.id in checkedWalletIds,
isLocked = isLocked && wallet.id != selectedWalletId,
)
}
}
walletSection(multiCurrencyWallets)
walletSection(singleCurrencyWallets)
}
}

View file

@ -0,0 +1,10 @@
package com.tangem.tap.features.walletSelector.ui.model
import androidx.compose.runtime.Immutable
@Immutable
internal data class RenameWalletDialog(
val currentName: String,
val onApply: (newName: String) -> Unit,
val onCancel: () -> Unit,
)

View file

@ -0,0 +1,46 @@
package com.tangem.tap.features.walletSelector.ui.model
import androidx.compose.runtime.Immutable
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.wallet.R
@Immutable
internal data class UserWalletItem(
val id: String,
val name: String,
val imageUrl: String,
val balance: Balance,
val type: Type,
) {
@Immutable
data class Balance(
val amount: String,
val isLoading: Boolean,
) {
companion object {
val Loading = Balance(
amount = "0.00",
isLoading = true,
)
}
}
sealed interface Type {
val headerText: TextReference
@Immutable
class MultiCurrency(
val tokensCount: Int,
val cardsInWallet: Int,
) : Type {
override val headerText: TextReference
get() = TextReference.Res(R.string.user_wallet_list_multi_header)
}
@Immutable
class SingleCurrency(val tokenName: String) : Type {
override val headerText: TextReference
get() = TextReference.Res(R.string.user_wallet_list_single_header)
}
}
}