Updated on 2026-08-14
This commit is contained in:
parent
ae9eb162f6
commit
27d1bf4b9e
21 changed files with 291 additions and 264 deletions
|
|
@ -78,7 +78,6 @@ internal class AddAddressModel @Inject constructor(
|
|||
ChosenNetworks(
|
||||
address = validation.address,
|
||||
matched = matched,
|
||||
displayed = displayedNetworks(matched, selected),
|
||||
selected = selectedNetworks(matched, selected),
|
||||
)
|
||||
}
|
||||
|
|
@ -86,7 +85,7 @@ internal class AddAddressModel @Inject constructor(
|
|||
.stateIn(
|
||||
modelScope,
|
||||
SharingStarted.Eagerly,
|
||||
ChosenNetworks(address = "", matched = emptyList(), displayed = emptyList(), selected = emptyList()),
|
||||
ChosenNetworks(address = "", matched = emptyList(), selected = emptyList()),
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -161,7 +160,6 @@ internal class AddAddressModel @Inject constructor(
|
|||
UpdateAddressValidationTransformer(
|
||||
address = networks.address,
|
||||
matchedBlockchains = networks.matched,
|
||||
displayedBlockchains = networks.displayed,
|
||||
selectedBlockchains = networks.selected,
|
||||
isMemoInvalid = memoInvalid,
|
||||
duplicateName = duplicate,
|
||||
|
|
@ -263,12 +261,6 @@ internal class AddAddressModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
/** What the network block shows: all matched networks until the user narrows them down, then the picked subset. */
|
||||
private fun displayedNetworks(matched: List<Blockchain>, selected: Set<String>?): List<Blockchain> {
|
||||
if (selected == null) return matched
|
||||
return matched.filter { it.toNetworkId() in selected }
|
||||
}
|
||||
|
||||
/**
|
||||
* What is actually selected for saving. A single matched network is auto-selected (there is nothing to choose and
|
||||
* the selection screen can't be opened); otherwise the user must pick explicitly before saving.
|
||||
|
|
@ -290,7 +282,6 @@ internal class AddAddressModel @Inject constructor(
|
|||
private data class ChosenNetworks(
|
||||
val address: String,
|
||||
val matched: List<Blockchain>,
|
||||
val displayed: List<Blockchain>,
|
||||
val selected: List<Blockchain>,
|
||||
) {
|
||||
/** The first selected network that supports a memo / destination tag, if any. */
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@ internal class UpdateAddressInputTransformer(
|
|||
) : Transformer<AddAddressUM> {
|
||||
|
||||
override fun transform(prevState: AddAddressUM): AddAddressUM {
|
||||
val chosenNetworkState = when {
|
||||
value.isBlank() -> ChosenNetworkStateUM.Hidden
|
||||
prevState.chosenNetworkStateUM is ChosenNetworkStateUM.Result -> prevState.chosenNetworkStateUM
|
||||
else -> ChosenNetworkStateUM.Loading
|
||||
val chosenNetworkState = when (prevState.chosenNetworkStateUM) {
|
||||
is ChosenNetworkStateUM.Result,
|
||||
ChosenNetworkStateUM.SelectNetwork,
|
||||
-> if (value.isBlank()) ChosenNetworkStateUM.Hidden else prevState.chosenNetworkStateUM
|
||||
else -> if (value.isBlank()) ChosenNetworkStateUM.Hidden else ChosenNetworkStateUM.Loading
|
||||
}
|
||||
val memoField = if (value.isBlank()) {
|
||||
prevState.memoField.copy(isVisible = false, value = "", isError = false)
|
||||
|
|
|
|||
|
|
@ -15,20 +15,19 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
/**
|
||||
* Reflects the result of validating an address (and its memo) in the UI.
|
||||
*
|
||||
* [matchedBlockchains] are all supported networks the address resolves to. [displayedBlockchains] is what the network
|
||||
* block shows — all matched networks until the user narrows them down on the SelectNetworks screen, then the picked
|
||||
* subset. [selectedBlockchains] is what is actually chosen for saving (a single match is auto-selected; for several
|
||||
* matches the user must pick explicitly). While the address is blank or matches nothing the network selector stays
|
||||
* [ChosenNetworkStateUM.Hidden]; an invalid (non-empty, matching nothing) address surfaces the error in the field label.
|
||||
* [matchedBlockchains] are all supported networks the address resolves to. [selectedBlockchains] is what is actually
|
||||
* chosen for saving (a single match is auto-selected; for several matches the user must pick explicitly on the
|
||||
* SelectNetworks screen). The network block shows only the selected networks — while several match but none is picked
|
||||
* yet it prompts with [ChosenNetworkStateUM.SelectNetwork] rather than showing every matched network. While the address
|
||||
* is blank or matches nothing the network selector stays [ChosenNetworkStateUM.Hidden]; an invalid (non-empty, matching
|
||||
* nothing) address surfaces the error in the field label.
|
||||
*
|
||||
* The confirm button is enabled only once at least one network is actually selected (and the memo, if any, is valid) —
|
||||
* showing the available networks is not the same as selecting them. The memo field is shown when a selected network
|
||||
* supports transaction extras; [isMemoInvalid] marks a malformed memo.
|
||||
* The confirm button is enabled only once at least one network is actually selected (and the memo, if any, is valid).
|
||||
* The memo field is shown when a selected network supports transaction extras; [isMemoInvalid] marks a malformed memo.
|
||||
*/
|
||||
internal class UpdateAddressValidationTransformer(
|
||||
private val address: String,
|
||||
private val matchedBlockchains: List<Blockchain>,
|
||||
private val displayedBlockchains: List<Blockchain>,
|
||||
private val selectedBlockchains: List<Blockchain>,
|
||||
private val isMemoInvalid: Boolean,
|
||||
private val duplicateName: String?,
|
||||
|
|
@ -40,14 +39,14 @@ internal class UpdateAddressValidationTransformer(
|
|||
val isDuplicate = duplicateName != null
|
||||
val isError = isInvalidAddress || isDuplicate
|
||||
|
||||
val chosenNetworkState = if (hasMatch) {
|
||||
ChosenNetworkStateUM.Result(
|
||||
networkUMList = displayedBlockchains.map(ChosenNetworkConverter()::convert).toImmutableList(),
|
||||
val chosenNetworkState = when {
|
||||
!hasMatch -> ChosenNetworkStateUM.Hidden
|
||||
selectedBlockchains.isEmpty() -> ChosenNetworkStateUM.SelectNetwork
|
||||
else -> ChosenNetworkStateUM.Result(
|
||||
networkUMList = selectedBlockchains.map(ChosenNetworkConverter()::convert).toImmutableList(),
|
||||
// A single matched network leaves nothing to choose, so the selection screen is not opened.
|
||||
isClickable = matchedBlockchains.size > 1,
|
||||
)
|
||||
} else {
|
||||
ChosenNetworkStateUM.Hidden
|
||||
}
|
||||
|
||||
val label = when {
|
||||
|
|
|
|||
|
|
@ -19,9 +19,8 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
|
|
@ -36,19 +35,14 @@ internal fun AddAddressContent(state: AddAddressUM, modifier: Modifier = Modifie
|
|||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(color = TangemTheme.colors3.bg.primary)
|
||||
.systemBarsPadding(),
|
||||
.navigationBarsPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
TangemTopBar(
|
||||
TangemTopNavigation(
|
||||
title = resourceReference(R.string.address_book_add_address),
|
||||
startContent = {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_back_24),
|
||||
onClick = state.onBackClick,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
)
|
||||
},
|
||||
contentAlign = TangemTopNavigation.ContentAlign.Center,
|
||||
blurBackground = false,
|
||||
onBack = state.onBackClick,
|
||||
)
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier
|
||||
|
|
@ -135,6 +129,7 @@ private fun NetworkSelector(chosenNetworkStateUM: AddAddressUM.ChosenNetworkStat
|
|||
when (networkState) {
|
||||
AddAddressUM.ChosenNetworkStateUM.Hidden -> Box(modifier = Modifier.fillMaxWidth())
|
||||
AddAddressUM.ChosenNetworkStateUM.Loading,
|
||||
AddAddressUM.ChosenNetworkStateUM.SelectNetwork,
|
||||
is AddAddressUM.ChosenNetworkStateUM.Result,
|
||||
-> NetworkBlock(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ internal fun NetworkBlock(
|
|||
chosenNetworkStateUM: AddAddressUM.ChosenNetworkStateUM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isClickable = chosenNetworkStateUM is AddAddressUM.ChosenNetworkStateUM.Result &&
|
||||
chosenNetworkStateUM.isClickable
|
||||
val isClickable = chosenNetworkStateUM is AddAddressUM.ChosenNetworkStateUM.SelectNetwork ||
|
||||
chosenNetworkStateUM is AddAddressUM.ChosenNetworkStateUM.Result && chosenNetworkStateUM.isClickable
|
||||
TangemRow(
|
||||
onClick = if (isClickable) onNetworkSelectClick else null,
|
||||
verticalAlignment = TangemRowVerticalAlignment.Center,
|
||||
|
|
@ -62,6 +62,7 @@ internal fun NetworkBlock(
|
|||
endSlot = {
|
||||
when (chosenNetworkStateUM) {
|
||||
AddAddressUM.ChosenNetworkStateUM.Loading -> TangemLoader(size = TangemLoaderSize.X20)
|
||||
AddAddressUM.ChosenNetworkStateUM.SelectNetwork -> SelectNetworkTextBlock()
|
||||
is AddAddressUM.ChosenNetworkStateUM.Result -> NetworkRow(chosenNetworkStateUM = chosenNetworkStateUM)
|
||||
AddAddressUM.ChosenNetworkStateUM.Hidden -> Unit
|
||||
}
|
||||
|
|
@ -69,6 +70,18 @@ internal fun NetworkBlock(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SelectNetworkTextBlock() {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.address_book_select_network),
|
||||
style = TangemTheme.typography3.body.medium,
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
)
|
||||
ChevronIcon()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NetworkRow(chosenNetworkStateUM: AddAddressUM.ChosenNetworkStateUM.Result) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
|
|
@ -120,7 +133,7 @@ private fun OverlappingNetworkIcons(networks: ImmutableList<NetworkUM>) {
|
|||
modifier = Modifier
|
||||
.padding(start = NetworkIconStep * index)
|
||||
.networkIconRing()
|
||||
.size(24.dp),
|
||||
.size(20.dp),
|
||||
)
|
||||
}
|
||||
if (remaining > 0) {
|
||||
|
|
@ -129,7 +142,7 @@ private fun OverlappingNetworkIcons(networks: ImmutableList<NetworkUM>) {
|
|||
.padding(start = NetworkIconStep * visible.size)
|
||||
.networkIconRing()
|
||||
.background(color = TangemTheme.colors3.bg.tertiary)
|
||||
.heightIn(min = 24.dp)
|
||||
.heightIn(min = 20.dp)
|
||||
.padding(vertical = 2.dp, horizontal = 4.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
|
|
@ -155,7 +168,7 @@ private fun Modifier.networkIconRing(): Modifier = this
|
|||
private fun ChevronIcon() {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.padding(start = 4.dp)
|
||||
.size(20.dp),
|
||||
tint = TangemTheme.colors3.icon.secondary,
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_select_18_24),
|
||||
|
|
@ -168,6 +181,11 @@ private fun ChevronIcon() {
|
|||
private fun Preview_NetworkBlock() {
|
||||
TangemThemePreviewRedesign {
|
||||
Column {
|
||||
NetworkBlock(
|
||||
onNetworkSelectClick = {},
|
||||
chosenNetworkStateUM = AddAddressUM.ChosenNetworkStateUM.SelectNetwork,
|
||||
)
|
||||
SpacerH12()
|
||||
NetworkBlock(
|
||||
onNetworkSelectClick = {},
|
||||
chosenNetworkStateUM = AddAddressUM.ChosenNetworkStateUM.Result(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ internal data class AddAddressUM(
|
|||
/** A non-blank address is being validated against the supported networks. */
|
||||
data object Loading : ChosenNetworkStateUM
|
||||
|
||||
/**
|
||||
* The address matched several networks but the user hasn't picked any yet — the row prompts them to open the
|
||||
* selection screen instead of showing network icons.
|
||||
*/
|
||||
data object SelectNetwork : ChosenNetworkStateUM
|
||||
|
||||
/**
|
||||
* A valid address resolved to [networkUMList] (the currently selected networks). [isClickable] is `false` when
|
||||
* the address matched only a single network — there is nothing to choose, so the network-selection screen is
|
||||
|
|
|
|||
|
|
@ -19,15 +19,14 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
|
||||
import com.tangem.core.ui.ds.image.TangemIcon
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
||||
import com.tangem.core.ui.ds2.button.Close
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_copy_20
|
||||
import com.tangem.core.ui.res.generated.icons.ic_cross_24
|
||||
import com.tangem.features.addressbook.addressinfo.ui.state.AddressInfoUM
|
||||
import com.tangem.features.addressbook.impl.R
|
||||
|
||||
|
|
@ -42,16 +41,10 @@ internal fun AddressInfoBottomSheet(state: AddressInfoUM, onDismiss: () -> Unit)
|
|||
type = TangemBottomSheetType.Modal,
|
||||
containerColor = TangemTheme.colors3.bg.secondary,
|
||||
title = {
|
||||
TangemTopBar(
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
endContent = {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_cross_24),
|
||||
onClick = onDismiss,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
)
|
||||
},
|
||||
TangemTopNavigation(
|
||||
windowInsets = WindowInsets(0),
|
||||
blurBackground = false,
|
||||
endButton = { TangemButton.Close(onClick = onDismiss) },
|
||||
)
|
||||
},
|
||||
content = {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ import com.tangem.common.ui.account.AccountIconUM
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
|
||||
import com.tangem.core.ui.ds.image.TangemIcon
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
||||
import com.tangem.core.ui.ds2.row.TangemRow
|
||||
import com.tangem.core.ui.ds2.row.TangemRowText
|
||||
import com.tangem.core.ui.ds2.row.TangemRowTextRole
|
||||
|
|
@ -51,18 +51,13 @@ internal fun AddressSelectorBottomSheet(
|
|||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
containerColor = TangemTheme.colors3.bg.primary,
|
||||
type = TangemBottomSheetType.Modal,
|
||||
title = {
|
||||
TangemTopBar(
|
||||
TangemTopNavigation(
|
||||
title = resourceReference(R.string.address_book_choose_address),
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
endContent = {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_close_24),
|
||||
onClick = onDismiss,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
)
|
||||
},
|
||||
contentAlign = TangemTopNavigation.ContentAlign.Center,
|
||||
blurBackground = false,
|
||||
onClose = onDismiss,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ internal fun ContactRow(contact: ContactUM, modifier: Modifier = Modifier) {
|
|||
AccountIcon(
|
||||
name = stringReference(contact.name),
|
||||
icon = contact.icon,
|
||||
size = AccountIconSize.Contact,
|
||||
size = AccountIconSize.ContactDefault,
|
||||
)
|
||||
},
|
||||
titleSlot = {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
|
@ -28,16 +30,17 @@ import com.tangem.core.ui.components.account.AccountIconSize
|
|||
import com.tangem.core.ui.components.block.BlockCard
|
||||
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||
import com.tangem.core.ui.components.fields.AutoSizeTextField
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.ds.image.TangemIcon
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.row.TangemRow
|
||||
import com.tangem.core.ui.ds2.row.TangemRowText
|
||||
import com.tangem.core.ui.ds2.row.TangemRowTextRole
|
||||
import com.tangem.core.ui.ds2.row.TangemRowVerticalAlignment
|
||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
|
@ -53,31 +56,21 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
|
||||
@Composable
|
||||
internal fun EditContactContent(state: EditContactUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(color = TangemTheme.colors3.bg.primary)
|
||||
.imePadding()
|
||||
.systemBarsPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
.background(color = TangemTheme.colors3.bg.primary),
|
||||
) {
|
||||
TangemTopBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
title = state.title,
|
||||
endContent = {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_close_24),
|
||||
onClick = state.onCloseClick,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
)
|
||||
},
|
||||
)
|
||||
var topBarHeightPx by remember { mutableIntStateOf(0) }
|
||||
val topBarHeight = with(LocalDensity.current) { topBarHeightPx.toDp() }
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
.fillMaxSize()
|
||||
.hazeSourceTangem()
|
||||
.background(color = TangemTheme.colors3.bg.primary)
|
||||
.imePadding()
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
val minContentHeight = maxHeight
|
||||
Column(
|
||||
|
|
@ -88,7 +81,8 @@ internal fun EditContactContent(state: EditContactUM, modifier: Modifier = Modif
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.heightIn(min = minContentHeight)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = topBarHeight + 12.dp, bottom = 12.dp),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
ContactSummary(state = state)
|
||||
|
|
@ -110,6 +104,15 @@ internal fun EditContactContent(state: EditContactUM, modifier: Modifier = Modif
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
TangemTopNavigation(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.onSizeChanged { topBarHeightPx = it.height },
|
||||
title = state.title,
|
||||
onClose = state.onCloseClick,
|
||||
contentAlign = TangemTopNavigation.ContentAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +300,6 @@ private fun DeleteContactButton(onClick: () -> Unit, modifier: Modifier = Modifi
|
|||
text = stringResourceSafe(R.string.address_book_delete_contact),
|
||||
style = TangemTheme.typography3.body.medium,
|
||||
color = TangemTheme.colors3.text.status.error,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
|
|
@ -333,7 +335,7 @@ private fun ContactSummary(state: EditContactUM) {
|
|||
AccountIcon(
|
||||
name = stringReference(avatarName),
|
||||
icon = state.portfolioIcon,
|
||||
size = AccountIconSize.RedesignLarge,
|
||||
size = AccountIconSize.ContactLarge,
|
||||
)
|
||||
|
||||
SpacerH(28.dp)
|
||||
|
|
|
|||
|
|
@ -95,7 +95,10 @@ internal class UpdateAddressBookListContentTransformer(
|
|||
isActive = false,
|
||||
onActiveChange = onActiveChange,
|
||||
onClearClick = onClearQuery,
|
||||
onCloseClick = { onActiveChange(false) },
|
||||
onCloseClick = {
|
||||
onClearQuery()
|
||||
onActiveChange(false)
|
||||
},
|
||||
)
|
||||
|
||||
private fun buildChips(matchingWalletIds: List<String>, effectiveSelected: String?) = buildList {
|
||||
|
|
|
|||
|
|
@ -15,14 +15,13 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_chevron_left_20
|
||||
import com.tangem.core.ui.res.generated.icons.ic_sign_plus_20
|
||||
|
||||
@Composable
|
||||
|
|
@ -35,17 +34,11 @@ internal fun AddressBookEmptyScreen(
|
|||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
TangemTopBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
TangemTopNavigation(
|
||||
title = resourceReference(R.string.address_book_title),
|
||||
startContent = {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_chevron_left_20),
|
||||
onClick = onBackClick,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
)
|
||||
},
|
||||
contentAlign = TangemTopNavigation.ContentAlign.Center,
|
||||
blurBackground = false,
|
||||
onBack = onBackClick,
|
||||
)
|
||||
NoContactInfo(onAddClick = onAddContactClick)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
||||
import com.tangem.core.ui.ds2.search.TangemSearch
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
|
|
@ -44,23 +44,15 @@ internal fun AddressBookListScreen(
|
|||
val density = LocalDensity.current
|
||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
Column(modifier = modifier.fillMaxSize()) {
|
||||
TangemTopBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
TangemTopNavigation(
|
||||
title = resourceReference(R.string.address_book_title),
|
||||
startContent = when (state.contentMode) {
|
||||
is ContentMode.Default -> {
|
||||
{
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_chevron_left_20),
|
||||
onClick = onBackClick,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
)
|
||||
}
|
||||
}
|
||||
contentAlign = TangemTopNavigation.ContentAlign.Center,
|
||||
blurBackground = false,
|
||||
onBack = when (state.contentMode) {
|
||||
is ContentMode.Default -> onBackClick
|
||||
ContentMode.Select -> null
|
||||
},
|
||||
endContent = {
|
||||
endButton = {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(
|
||||
imageVector = when (state.contentMode) {
|
||||
|
|
|
|||
|
|
@ -11,30 +11,20 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.shimmers.ProvideTangemShimmer
|
||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_chevron_left_20
|
||||
|
||||
@Composable
|
||||
internal fun AddressBookListShimmer(onBackClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier.fillMaxSize()) {
|
||||
TangemTopBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
TangemTopNavigation(
|
||||
title = resourceReference(R.string.address_book_title),
|
||||
startContent = {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_chevron_left_20),
|
||||
onClick = onBackClick,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
)
|
||||
},
|
||||
contentAlign = TangemTopNavigation.ContentAlign.Center,
|
||||
blurBackground = false,
|
||||
onBack = onBackClick,
|
||||
)
|
||||
ProvideTangemShimmer {
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ internal class UpdateSelectNetworksInitialStateTransformer(
|
|||
searchBar = prevState.searchBar.copy(
|
||||
onQueryChange = onQueryChange,
|
||||
onActiveChange = onActiveChange,
|
||||
onCloseClick = { onActiveChange(false) },
|
||||
onCloseClick = {
|
||||
onQueryChange("")
|
||||
onActiveChange(false)
|
||||
},
|
||||
onClearClick = { onQueryChange("") },
|
||||
),
|
||||
doneButton = prevState.doneButton.copy(onClick = onDoneClick),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -28,16 +29,17 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.checkbox.TangemCheckmark
|
||||
import com.tangem.core.ui.ds2.search.TangemSearch
|
||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.res.LocalHapticManager
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_search_24
|
||||
import com.tangem.features.addressbook.selectnetworks.ui.state.SelectNetworksUM
|
||||
import com.tangem.features.addressbook.selectnetworks.ui.state.SelectNetworksUM.NetworkItemUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -48,18 +50,13 @@ internal fun SelectNetworksContent(state: SelectNetworksUM, modifier: Modifier =
|
|||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(color = TangemTheme.colors3.bg.primary)
|
||||
.systemBarsPadding(),
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
TangemTopBar(
|
||||
TangemTopNavigation(
|
||||
title = resourceReference(R.string.common_choose_network),
|
||||
startContent = {
|
||||
TangemButton(
|
||||
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_back_24),
|
||||
onClick = state.onBackClick,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
)
|
||||
},
|
||||
contentAlign = TangemTopNavigation.ContentAlign.Center,
|
||||
blurBackground = false,
|
||||
onBack = state.onBackClick,
|
||||
)
|
||||
TangemSearch(
|
||||
state = state.searchBar,
|
||||
|
|
@ -67,46 +64,89 @@ internal fun SelectNetworksContent(state: SelectNetworksUM, modifier: Modifier =
|
|||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
)
|
||||
val doneButtonVerticalPadding = 12.dp
|
||||
val doneButtonAreaHeight = 48.dp + doneButtonVerticalPadding * 2
|
||||
Box(
|
||||
AnimatedContent(
|
||||
targetState = state.networks.isNotEmpty(),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = doneButtonAreaHeight)
|
||||
.background(
|
||||
color = TangemTheme.colors3.bg.secondary,
|
||||
shape = RoundedCornerShape(24.dp),
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
item {
|
||||
NetworksHeader(
|
||||
selectAllButton = state.selectAllButton,
|
||||
onSelectAllClick = state.onSelectAllClick,
|
||||
)
|
||||
}
|
||||
items(items = state.networks, key = NetworkItemUM::id) { item ->
|
||||
NetworkRow(item = item)
|
||||
) { hasNetworks ->
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
if (hasNetworks) {
|
||||
Content(state = state)
|
||||
} else {
|
||||
NothingFoundContent(modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
TangemButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = doneButtonVerticalPadding)
|
||||
.imePadding(),
|
||||
onClick = state.doneButton.onClick,
|
||||
isEnabled = state.doneButton.isEnabled,
|
||||
size = TangemButton.Size.X12,
|
||||
text = state.doneButton.text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.Content(state: SelectNetworksUM, modifier: Modifier = Modifier) {
|
||||
val doneButtonVerticalPadding = 12.dp
|
||||
val doneButtonAreaHeight = 48.dp + doneButtonVerticalPadding * 2
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = doneButtonAreaHeight)
|
||||
.background(
|
||||
color = TangemTheme.colors3.bg.secondary,
|
||||
shape = RoundedCornerShape(24.dp),
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
item {
|
||||
NetworksHeader(
|
||||
selectAllButton = state.selectAllButton,
|
||||
onSelectAllClick = state.onSelectAllClick,
|
||||
)
|
||||
}
|
||||
items(items = state.networks, key = NetworkItemUM::id) { item ->
|
||||
NetworkRow(item = item)
|
||||
}
|
||||
}
|
||||
TangemButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = doneButtonVerticalPadding)
|
||||
.imePadding(),
|
||||
onClick = state.doneButton.onClick,
|
||||
isEnabled = state.doneButton.isEnabled,
|
||||
size = TangemButton.Size.X12,
|
||||
text = state.doneButton.text,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NothingFoundContent(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.imePadding(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.background(color = TangemTheme.colors3.bg.opaque.primary, shape = CircleShape),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.ic_search_24,
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors3.icon.secondary,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 32.dp),
|
||||
text = stringResourceSafe(R.string.address_book_search_no_results),
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ internal class AddAddressModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN address matching several networks WHEN validated THEN all shown AND clickable`() = runTest {
|
||||
fun `GIVEN address matching several networks WHEN validated THEN prompts to select network`() = runTest {
|
||||
// Arrange
|
||||
every { supportedNetworksMatcher.match(ADDRESS) } returns listOf(Blockchain.Ethereum, Blockchain.BSC)
|
||||
val model = createModel(testScope = this)
|
||||
|
|
@ -251,11 +251,8 @@ internal class AddAddressModelTest {
|
|||
model.state.value.onAddressChange(ADDRESS)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert — all matched networks are shown by default; the block opens the selection screen to narrow them.
|
||||
val result = model.state.value.chosenNetworkStateUM as ChosenNetworkStateUM.Result
|
||||
assertThat(result.networkUMList.map { it.networkName })
|
||||
.containsExactly(Blockchain.Ethereum.fullName, Blockchain.BSC.fullName)
|
||||
assertThat(result.isClickable).isTrue()
|
||||
// Assert — nothing is selected yet, so the block only prompts the user to open the selection screen.
|
||||
assertThat(model.state.value.chosenNetworkStateUM).isEqualTo(ChosenNetworkStateUM.SelectNetwork)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -348,19 +345,19 @@ internal class AddAddressModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN resolved networks WHEN address edited THEN keeps result without flashing loading`() = runTest {
|
||||
// Arrange
|
||||
fun `GIVEN resolved networks WHEN address edited THEN keeps state without flashing loading`() = runTest {
|
||||
// Arrange — several networks match, so the resolved state is the "select network" prompt.
|
||||
every { supportedNetworksMatcher.match(any()) } returns listOf(Blockchain.Ethereum, Blockchain.BSC)
|
||||
val model = createModel(testScope = this)
|
||||
model.state.value.onAddressChange(ADDRESS)
|
||||
advanceUntilIdle()
|
||||
assertThat(model.state.value.chosenNetworkStateUM).isInstanceOf(ChosenNetworkStateUM.Result::class.java)
|
||||
assertThat(model.state.value.chosenNetworkStateUM).isEqualTo(ChosenNetworkStateUM.SelectNetwork)
|
||||
|
||||
// Act — keep typing; validation is pending again.
|
||||
model.state.value.onAddressChange(ADDRESS + "00")
|
||||
|
||||
// Assert — the resolved networks stay on screen (no spinner), but the button is blocked while validating.
|
||||
assertThat(model.state.value.chosenNetworkStateUM).isInstanceOf(ChosenNetworkStateUM.Result::class.java)
|
||||
// Assert — the resolved state stays on screen (no spinner), but the button is blocked while validating.
|
||||
assertThat(model.state.value.chosenNetworkStateUM).isEqualTo(ChosenNetworkStateUM.SelectNetwork)
|
||||
assertThat(model.state.value.buttonUM.isEnabled).isFalse()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
|
||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.features.commonfeatures.impl.R
|
||||
import com.tangem.features.commonfeatures.impl.portfolioselector.entity.PortfolioSelectorUM
|
||||
|
||||
@Composable
|
||||
|
|
@ -27,20 +27,21 @@ internal fun PortfolioSelectorBS(
|
|||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = onDismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
onBack = onBack,
|
||||
scrollableContent = false,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
containerColor = TangemTheme.colors3.bg.primary,
|
||||
type = TangemBottomSheetType.Modal,
|
||||
title = {
|
||||
TangemModalBottomSheetTitle(
|
||||
TangemTopNavigation(
|
||||
title = state.title,
|
||||
startIconRes = R.drawable.ic_back_24,
|
||||
onStartClick = onBack,
|
||||
contentAlign = TangemTopNavigation.ContentAlign.Center,
|
||||
blurBackground = false,
|
||||
onClose = onDismiss,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
|
|
|
|||
|
|
@ -88,15 +88,15 @@ private fun AddressWithMemoBlock(
|
|||
val contactIcon = address.contactIcon
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = contactName ?: address.value,
|
||||
text = address.value,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.testTag(SendConfirmScreenTestTags.RECIPIENT_ADDRESS),
|
||||
)
|
||||
val recipient = if (contactName != null) address.value else address.briefBlockchainAddress
|
||||
if (!recipient.isNullOrBlank()) {
|
||||
val blockchainAddress = address.briefBlockchainAddress
|
||||
if (!blockchainAddress.isNullOrBlank()) {
|
||||
Text(
|
||||
text = recipient,
|
||||
text = blockchainAddress,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.testTag(SendConfirmScreenTestTags.BLOCKCHAIN_ADDRESS),
|
||||
|
|
@ -107,7 +107,7 @@ private fun AddressWithMemoBlock(
|
|||
AccountIcon(
|
||||
name = stringReference(contactName),
|
||||
icon = contactIcon,
|
||||
size = AccountIconSize.Medium,
|
||||
size = AccountIconSize.ContactDefault,
|
||||
modifier = Modifier.testTag(SendConfirmScreenTestTags.RECIPIENT_ADDRESS_ICON),
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue