Updated on 2026-08-14
This commit is contained in:
parent
0cc270adc0
commit
47412b1d63
28 changed files with 1110 additions and 2 deletions
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -25,4 +25,5 @@ enum class AppScreen(
|
|||
ReferralProgram,
|
||||
Welcome,
|
||||
SaveWallet(isDialogFragment = true),
|
||||
WalletSelector(isDialogFragment = true),
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.tap.features.walletSelector.redux
|
||||
|
||||
import org.rekotlin.StateType
|
||||
|
||||
class WalletSelectorState : StateType
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue