Updated on 2026-08-14
This commit is contained in:
commit
b77f1fb448
59 changed files with 1989 additions and 533 deletions
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -15,10 +18,25 @@ internal object TransactionDomainModule {
|
|||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideGetUseCase(
|
||||
fun provideGetFeeUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetFeeUseCase {
|
||||
return GetFeeUseCase(walletManagersFacade, dispatchers)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideSendTransactionUseCase(
|
||||
isDemoCardUseCase: IsDemoCardUseCase,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
): SendTransactionUseCase {
|
||||
return SendTransactionUseCase(
|
||||
isDemoCardUseCase = isDemoCardUseCase,
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +63,7 @@ internal fun SettingsSwitchItem(item: Item.Switch, modifier: Modifier = Modifier
|
|||
checked = item.isChecked,
|
||||
enabled = item.isEnabled,
|
||||
onCheckedChange = item.onCheckedChange,
|
||||
checkedColor = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import androidx.compose.material.Text
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
|
@ -162,11 +163,12 @@ fun CardWithIcon(
|
|||
* >Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun IconWithTitleAndDescription(
|
||||
internal fun IconWithTitleAndDescription(
|
||||
title: String,
|
||||
description: String,
|
||||
description: String?,
|
||||
icon: @Composable () -> Unit,
|
||||
additionalContent: @Composable () -> Unit = {},
|
||||
iconBackground: Color = TangemTheme.colors.background.secondary,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -182,7 +184,7 @@ fun IconWithTitleAndDescription(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.secondary,
|
||||
color = iconBackground,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.height(TangemTheme.dimens.size40)
|
||||
|
|
@ -205,12 +207,14 @@ fun IconWithTitleAndDescription(
|
|||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
SpacerH4()
|
||||
Text(
|
||||
text = description,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
if (description != null) {
|
||||
SpacerH4()
|
||||
Text(
|
||||
text = description,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
additionalContent()
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
@Composable
|
||||
fun TangemSwitch(
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
checkedColor: Color = TangemTheme.colors.icon.accent,
|
||||
checkedColor: Color = TangemTheme.colors.control.checked,
|
||||
uncheckedColor: Color = TangemTheme.colors.icon.informative,
|
||||
size: Dp = 48.dp,
|
||||
checked: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.material.ExperimentalMaterialApi
|
|||
import androidx.compose.material.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
|
|
@ -36,6 +37,28 @@ fun WarningCard(title: String, description: String, icon: @Composable (() -> Uni
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A card with a warning icon to the left and title without description shown to the right of it.
|
||||
*
|
||||
* @param title title of the warning in bold
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=290%3A217&t=yMepJZTRh5bLkOoJ-1"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun WarningCardTitleOnly(title: String, icon: @Composable (() -> Unit)? = null) {
|
||||
WarningCardMaterial3Style(
|
||||
content = {
|
||||
WarningBody(
|
||||
title = title,
|
||||
description = null,
|
||||
icon = icon,
|
||||
iconBackground = TangemTheme.colors.button.disabled,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* [WarningCard], but clickable (with an 'greater then' icon to the left)
|
||||
*
|
||||
|
|
@ -105,7 +128,8 @@ fun RefreshableWaringCard(
|
|||
@Composable
|
||||
private fun WarningBody(
|
||||
title: String,
|
||||
description: String,
|
||||
description: String?,
|
||||
iconBackground: Color = TangemTheme.colors.background.secondary,
|
||||
icon: @Composable (() -> Unit)? = null,
|
||||
additionalContent: @Composable () -> Unit = {},
|
||||
) {
|
||||
|
|
@ -119,6 +143,7 @@ private fun WarningBody(
|
|||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
iconBackground = iconBackground,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -136,6 +161,20 @@ private fun WarningCardSurface(onClick: (() -> Unit)? = null, content: @Composab
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun WarningCardMaterial3Style(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) {
|
||||
Card(
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius16),
|
||||
backgroundColor = TangemTheme.colors.button.disabled,
|
||||
elevation = TangemTheme.dimens.elevation0,
|
||||
onClick = onClick ?: {},
|
||||
enabled = onClick != null,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
// endregion elements
|
||||
|
||||
// region Preview
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ import androidx.annotation.DrawableRes
|
|||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
|
@ -39,7 +42,10 @@ fun AppBarWithBackButtonAndIcon(
|
|||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size24)
|
||||
.clickable { onBackClick() },
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false),
|
||||
) { onBackClick() },
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
AnimatedContent(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.core.ui.components.fields
|
||||
package com.tangem.core.ui.components.fields.visualtransformations
|
||||
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.tangem.core.ui.components.transactions
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.toDateFormat
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
|
||||
/**
|
||||
* Common transaction done screen title
|
||||
*
|
||||
* @param titleRes title resource
|
||||
* @param date transaction timestamp in millis
|
||||
*/
|
||||
@Composable
|
||||
fun TransactionDoneTitle(@StringRes titleRes: Int, date: Long, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_empty_in_process_64),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing8)
|
||||
.size(TangemTheme.dimens.size64),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = titleRes),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing32),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.send_date_format, date.toDateFormat(), date.toTimeFormat()),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing4),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Previews
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TransactionDoneTitlePreview_Light() {
|
||||
TangemTheme {
|
||||
TransactionDoneTitle(
|
||||
titleRes = R.string.sent_transaction_sent_title,
|
||||
date = 0,
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TransactionDoneTitlePreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
TransactionDoneTitle(
|
||||
titleRes = R.string.sent_transaction_sent_title,
|
||||
date = 0,
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -7,18 +7,34 @@ import androidx.compose.ui.composed
|
|||
import androidx.compose.ui.draw.clip
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
fun Modifier.roundedShapeItemDecoration(currentIndex: Int, lastIndex: Int): Modifier = composed {
|
||||
val modifierWithHorizontalPadding = this.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
fun Modifier.roundedShapeItemDecoration(
|
||||
currentIndex: Int,
|
||||
lastIndex: Int,
|
||||
addDefaultPadding: Boolean = true,
|
||||
): Modifier = composed {
|
||||
val modifier = if (addDefaultPadding) this.padding(horizontal = TangemTheme.dimens.spacing16) else this
|
||||
val isSingleItem = currentIndex == 0 && lastIndex == 0
|
||||
when {
|
||||
isSingleItem -> {
|
||||
modifierWithHorizontalPadding
|
||||
.padding(top = TangemTheme.dimens.spacing14)
|
||||
modifier
|
||||
.then(
|
||||
if (addDefaultPadding) {
|
||||
Modifier.padding(top = TangemTheme.dimens.spacing14)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.clip(shape = TangemTheme.shapes.roundedCornersXMedium)
|
||||
}
|
||||
currentIndex == 0 -> {
|
||||
modifierWithHorizontalPadding
|
||||
.padding(top = TangemTheme.dimens.spacing14)
|
||||
modifier
|
||||
.then(
|
||||
if (addDefaultPadding) {
|
||||
Modifier.padding(top = TangemTheme.dimens.spacing14)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.clip(
|
||||
shape = RoundedCornerShape(
|
||||
topStart = TangemTheme.dimens.radius16,
|
||||
|
|
@ -27,7 +43,7 @@ fun Modifier.roundedShapeItemDecoration(currentIndex: Int, lastIndex: Int): Modi
|
|||
)
|
||||
}
|
||||
currentIndex == lastIndex -> {
|
||||
modifierWithHorizontalPadding
|
||||
modifier
|
||||
.clip(
|
||||
shape = RoundedCornerShape(
|
||||
bottomStart = TangemTheme.dimens.radius16,
|
||||
|
|
@ -35,6 +51,6 @@ fun Modifier.roundedShapeItemDecoration(currentIndex: Int, lastIndex: Int): Modi
|
|||
),
|
||||
)
|
||||
}
|
||||
else -> modifierWithHorizontalPadding
|
||||
else -> modifier
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.text.NumberFormat
|
||||
|
|
@ -24,6 +25,10 @@ object BigDecimalFormatter {
|
|||
return formatter.format(cryptoAmount) + "\u2009$cryptoCurrency"
|
||||
}
|
||||
|
||||
fun formatCryptoAmount(cryptoAmount: BigDecimal?, cryptoCurrency: CryptoCurrency): String {
|
||||
return formatCryptoAmount(cryptoAmount, cryptoCurrency.symbol, cryptoCurrency.decimals)
|
||||
}
|
||||
|
||||
fun formatFiatAmount(
|
||||
fiatAmount: BigDecimal?,
|
||||
fiatCurrencyCode: String,
|
||||
|
|
|
|||
10
core/ui/src/main/res/drawable/ic_empty_in_process_64.xml
Normal file
10
core/ui/src/main/res/drawable/ic_empty_in_process_64.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="64"
|
||||
android:viewportHeight="64">
|
||||
<path
|
||||
android:pathData="M28.384,0.616C27.896,0.128 27.104,0.128 26.616,0.616C26.128,1.104 26.128,1.896 26.616,2.384L31.002,6.769C17.519,7.294 6.75,18.389 6.75,32C6.75,45.945 18.055,57.25 32,57.25C45.945,57.25 57.25,45.945 57.25,32C57.25,31.31 56.69,30.75 56,30.75C55.31,30.75 54.75,31.31 54.75,32C54.75,44.564 44.564,54.75 32,54.75C19.435,54.75 9.25,44.564 9.25,32C9.25,19.785 18.878,9.818 30.959,9.273L26.616,13.616C26.128,14.104 26.128,14.896 26.616,15.384C27.104,15.872 27.896,15.872 28.384,15.384L34.884,8.884C35.372,8.396 35.372,7.604 34.884,7.116L28.384,0.616ZM39,22.75H40.25V24V26V26.518L39.884,26.884L33.768,33L39.884,39.116L40.25,39.482V40V42V43.25H39H25H23.75V42V40V39.482L24.116,39.116L30.232,33L24.116,26.884L23.75,26.518V26V24V22.75H25H39ZM26.25,25.482L32,31.232L37.75,25.482V25.25H26.25V25.482ZM37.75,40.518L32,34.768L26.25,40.518V40.75H37.75V40.518Z"
|
||||
android:fillColor="#C9C9C9"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
13
core/ui/src/main/res/drawable/ic_web_24.xml
Normal file
13
core/ui/src/main/res/drawable/ic_web_24.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h24v24h-24z"/>
|
||||
<path
|
||||
android:pathData="M12,22C10.633,22 9.342,21.737 8.125,21.212C6.908,20.688 5.846,19.971 4.938,19.063C4.029,18.154 3.313,17.092 2.787,15.875C2.263,14.658 2,13.367 2,12C2,10.617 2.263,9.321 2.787,8.113C3.313,6.904 4.029,5.846 4.938,4.938C5.846,4.029 6.908,3.313 8.125,2.787C9.342,2.263 10.633,2 12,2C13.383,2 14.679,2.263 15.887,2.787C17.096,3.313 18.154,4.029 19.063,4.938C19.971,5.846 20.688,6.904 21.212,8.113C21.737,9.321 22,10.617 22,12C22,13.367 21.737,14.658 21.212,15.875C20.688,17.092 19.971,18.154 19.063,19.063C18.154,19.971 17.096,20.688 15.887,21.212C14.679,21.737 13.383,22 12,22ZM12,19.95C12.433,19.35 12.808,18.725 13.125,18.075C13.442,17.425 13.7,16.733 13.9,16H10.1C10.3,16.733 10.558,17.425 10.875,18.075C11.192,18.725 11.567,19.35 12,19.95ZM9.4,19.55C9.1,19 8.837,18.429 8.613,17.837C8.387,17.246 8.2,16.633 8.05,16H5.1C5.583,16.833 6.188,17.558 6.912,18.175C7.637,18.792 8.467,19.25 9.4,19.55ZM14.6,19.55C15.533,19.25 16.362,18.792 17.087,18.175C17.813,17.558 18.417,16.833 18.9,16H15.95C15.8,16.633 15.613,17.246 15.387,17.837C15.163,18.429 14.9,19 14.6,19.55ZM4.25,14H7.65C7.6,13.667 7.563,13.337 7.537,13.012C7.512,12.688 7.5,12.35 7.5,12C7.5,11.65 7.512,11.313 7.537,10.988C7.563,10.663 7.6,10.333 7.65,10H4.25C4.167,10.333 4.104,10.663 4.063,10.988C4.021,11.313 4,11.65 4,12C4,12.35 4.021,12.688 4.063,13.012C4.104,13.337 4.167,13.667 4.25,14ZM9.65,14H14.35C14.4,13.667 14.438,13.337 14.462,13.012C14.488,12.688 14.5,12.35 14.5,12C14.5,11.65 14.488,11.313 14.462,10.988C14.438,10.663 14.4,10.333 14.35,10H9.65C9.6,10.333 9.563,10.663 9.538,10.988C9.512,11.313 9.5,11.65 9.5,12C9.5,12.35 9.512,12.688 9.538,13.012C9.563,13.337 9.6,13.667 9.65,14ZM16.35,14H19.75C19.833,13.667 19.896,13.337 19.938,13.012C19.979,12.688 20,12.35 20,12C20,11.65 19.979,11.313 19.938,10.988C19.896,10.663 19.833,10.333 19.75,10H16.35C16.4,10.333 16.438,10.663 16.462,10.988C16.487,11.313 16.5,11.65 16.5,12C16.5,12.35 16.487,12.688 16.462,13.012C16.438,13.337 16.4,13.667 16.35,14ZM15.95,8H18.9C18.417,7.167 17.813,6.442 17.087,5.825C16.362,5.208 15.533,4.75 14.6,4.45C14.9,5 15.163,5.571 15.387,6.162C15.613,6.754 15.8,7.367 15.95,8ZM10.1,8H13.9C13.7,7.267 13.442,6.575 13.125,5.925C12.808,5.275 12.433,4.65 12,4.05C11.567,4.65 11.192,5.275 10.875,5.925C10.558,6.575 10.3,7.267 10.1,8ZM5.1,8H8.05C8.2,7.367 8.387,6.754 8.613,6.162C8.837,5.571 9.1,5 9.4,4.45C8.467,4.75 7.637,5.208 6.912,5.825C6.188,6.442 5.583,7.167 5.1,8Z"
|
||||
android:fillColor="#1E1E1E"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -13,6 +13,10 @@ android {
|
|||
dependencies {
|
||||
implementation(deps.androidx.datastore)
|
||||
|
||||
implementation(deps.tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.card
|
||||
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.blockchain.common.CommonSigner
|
||||
import com.tangem.common.UserCodeType
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.common.core.UserCodeRequestPolicy
|
||||
|
|
@ -57,4 +58,16 @@ internal class DefaultCardSdkConfigRepository(
|
|||
}
|
||||
|
||||
override fun isAccessCodeSavingEnabled(): Boolean = preferencesDataSource.shouldSaveAccessCodes
|
||||
|
||||
override fun getCommonSigner(cardId: String?) = CommonSigner(
|
||||
tangemSdk = sdk,
|
||||
cardId = cardId,
|
||||
initialMessage = null,
|
||||
)
|
||||
|
||||
override fun isLinkedTerminal() = sdk.config.linkedTerminal
|
||||
|
||||
override fun setLinkedTerminal(isLinked: Boolean?) {
|
||||
sdk.config.linkedTerminal = isLinked
|
||||
}
|
||||
}
|
||||
|
|
@ -19,4 +19,8 @@ dependencies {
|
|||
|
||||
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.card.repository
|
||||
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.blockchain.common.CommonSigner
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
|
||||
/**
|
||||
|
|
@ -28,4 +29,13 @@ interface CardSdkConfigRepository {
|
|||
|
||||
/** Check if access code saving is enabled */
|
||||
fun isAccessCodeSavingEnabled(): Boolean
|
||||
|
||||
/** Get common signer by [cardId] */
|
||||
fun getCommonSigner(cardId: String?): CommonSigner
|
||||
|
||||
/** Check if linked terminal is enabled */
|
||||
fun isLinkedTerminal(): Boolean?
|
||||
|
||||
/** Set linked terminal by [isLinked] */
|
||||
fun setLinkedTerminal(isLinked: Boolean?)
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.blockchain.blockchains.solana.RentProvider
|
|||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.address.Address
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
|
|
@ -35,6 +36,7 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.util.EnumSet
|
||||
|
||||
@Suppress("LargeClass")
|
||||
// FIXME: Move to its own module and make internal
|
||||
|
|
@ -431,6 +433,58 @@ class DefaultWalletManagersFacade(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun validateTransaction(
|
||||
amount: Amount,
|
||||
fee: Amount?,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): EnumSet<TransactionError>? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
)
|
||||
return walletManager?.validateTransaction(amount, fee)
|
||||
}
|
||||
|
||||
override suspend fun createTransaction(
|
||||
amount: Amount,
|
||||
fee: Fee,
|
||||
memo: String?,
|
||||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): TransactionData? {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
)
|
||||
|
||||
val txData = walletManager?.createTransaction(amount, fee, destination)?.copy(
|
||||
extras = null, // todo add memo [[REDACTED_JIRA]]
|
||||
)
|
||||
|
||||
return txData
|
||||
}
|
||||
|
||||
override suspend fun sendTransaction(
|
||||
txData: TransactionData,
|
||||
signer: CommonSigner,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): SimpleResult {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
)
|
||||
return (walletManager as TransactionSender).send(txData, signer)
|
||||
}
|
||||
|
||||
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
|
||||
if (tokens.isEmpty()) return
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ package com.tangem.domain.walletmanager
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.blockchains.solana.RentProvider
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.address.Address
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
|
|
@ -19,6 +19,7 @@ import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.math.BigDecimal
|
||||
import java.util.EnumSet
|
||||
|
||||
// TODO: Move to its own module
|
||||
/**
|
||||
|
|
@ -161,4 +162,54 @@ interface WalletManagersFacade {
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Result<TransactionFee>?
|
||||
|
||||
/**
|
||||
* Validates transaction
|
||||
*
|
||||
* @param amount of transaction
|
||||
* @param fee of transaction
|
||||
* @param userWalletId selected wallet id
|
||||
* @param network network of currency
|
||||
*/
|
||||
suspend fun validateTransaction(
|
||||
amount: Amount,
|
||||
fee: Amount?,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): EnumSet<TransactionError>?
|
||||
|
||||
/**
|
||||
* Creates transaction [TransactionData]
|
||||
*
|
||||
* @param amount of transaction
|
||||
* @param fee of transaction
|
||||
* @param memo of transaction optional
|
||||
* @param destination address
|
||||
* @param userWalletId selected wallet id
|
||||
* @param network network of currency
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun createTransaction(
|
||||
amount: Amount,
|
||||
fee: Fee,
|
||||
memo: String?,
|
||||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): TransactionData?
|
||||
|
||||
/**
|
||||
* Sends transaction
|
||||
*
|
||||
* @param txData transaction data
|
||||
* @param signer card signer
|
||||
* @param userWalletId selected wallet id
|
||||
* @param network network of currency
|
||||
*/
|
||||
suspend fun sendTransaction(
|
||||
txData: TransactionData,
|
||||
signer: CommonSigner,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): SimpleResult
|
||||
}
|
||||
|
|
@ -12,4 +12,7 @@ android {
|
|||
dependencies {
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(deps.tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.domain.tokens.utils
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import java.math.BigDecimal
|
||||
|
||||
/** Converts `BigDecimal` [cryptoCurrency] to [Amount] */
|
||||
fun BigDecimal.convertToAmount(cryptoCurrency: CryptoCurrency) = Amount(
|
||||
currencySymbol = cryptoCurrency.symbol,
|
||||
value = this,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
type = when (cryptoCurrency) {
|
||||
is CryptoCurrency.Coin -> AmountType.Coin
|
||||
is CryptoCurrency.Token -> AmountType.Token(
|
||||
token = Token(
|
||||
symbol = cryptoCurrency.symbol,
|
||||
contractAddress = cryptoCurrency.contractAddress,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -14,10 +14,15 @@ dependencies {
|
|||
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Tangem SDKs */
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.blockchain)
|
||||
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.card)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.transaction.error
|
||||
|
||||
sealed class SendTransactionError {
|
||||
|
||||
object DemoCardError : SendTransactionError()
|
||||
|
||||
data class DataError(val message: String?) : SendTransactionError()
|
||||
|
||||
data class NetworkError(val message: String?) : SendTransactionError()
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
class SendTransactionUseCase(
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
txData: TransactionData,
|
||||
userWallet: UserWallet,
|
||||
network: Network,
|
||||
): Either<SendTransactionError?, Boolean> {
|
||||
val signer = cardSdkConfigRepository.getCommonSigner(
|
||||
userWallet.cardId,
|
||||
)
|
||||
|
||||
val linkedTerminal = cardSdkConfigRepository.isLinkedTerminal()
|
||||
if (userWallet.scanResponse.card.isStart2Coin) {
|
||||
cardSdkConfigRepository.setLinkedTerminal(false)
|
||||
}
|
||||
val sendResult = try {
|
||||
if (isDemoCardUseCase(cardId = userWallet.cardId)) {
|
||||
SendTransactionError.DemoCardError.left()
|
||||
} else {
|
||||
walletManagersFacade.sendTransaction(
|
||||
txData = txData,
|
||||
signer = signer,
|
||||
userWalletId = userWallet.walletId,
|
||||
network = network,
|
||||
).right()
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
cardSdkConfigRepository.setLinkedTerminal(linkedTerminal)
|
||||
SendTransactionError.DataError(ex.message).left()
|
||||
}
|
||||
|
||||
cardSdkConfigRepository.setLinkedTerminal(linkedTerminal)
|
||||
return sendResult.fold(
|
||||
ifRight = { result ->
|
||||
when (result) {
|
||||
is SimpleResult.Success -> true.right()
|
||||
is SimpleResult.Failure -> SendTransactionError.NetworkError(result.error.message).left()
|
||||
}
|
||||
},
|
||||
ifLeft = { it.left() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.managetokens.presentation.common.state
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal sealed class ChooseWalletState {
|
||||
data class Choose(
|
||||
val wallets: ImmutableList<WalletState>,
|
||||
val selectedWallet: WalletState?,
|
||||
val onChooseWalletClick: () -> Unit,
|
||||
val onCloseChoosingWalletClick: () -> Unit,
|
||||
) : ChooseWalletState()
|
||||
|
||||
object NoSelection : ChooseWalletState()
|
||||
|
||||
class Warning(val type: ChooseWalletWarning) : ChooseWalletState() {
|
||||
val message: TextReference
|
||||
get() = when (type) {
|
||||
ChooseWalletWarning.SINGLE_CURRENCY ->
|
||||
TextReference.Res(R.string.manage_tokens_wallet_support_only_one_network_title)
|
||||
ChooseWalletWarning.WALLET_INCOMPATIBLE ->
|
||||
TextReference.Res(R.string.manage_tokens_wallet_does_not_supported_blockchain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ChooseWalletWarning {
|
||||
SINGLE_CURRENCY,
|
||||
WALLET_INCOMPATIBLE,
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package com.tangem.managetokens.presentation.common.state
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.core.ui.extensions.getGreyedOutIconRes
|
||||
import com.tangem.managetokens.presentation.managetokens.state.TokenItemState
|
||||
|
||||
/**
|
||||
* Network item state
|
||||
*
|
||||
* @property name network name
|
||||
* @property protocolName network protocol name
|
||||
* @property id network id
|
||||
* @property blockchain blockchain
|
||||
* @property iconRes network icon id from resources
|
||||
*/
|
||||
internal sealed interface NetworkItemState {
|
||||
|
||||
val name: String
|
||||
val protocolName: String
|
||||
val id: String
|
||||
val blockchain: Blockchain
|
||||
|
||||
val iconRes: Int
|
||||
get() = when (this) {
|
||||
is Selectable -> this.iconResId
|
||||
is Toggleable -> this.iconResId.value
|
||||
}
|
||||
|
||||
/**
|
||||
* Network item state that can be added and deleted
|
||||
*
|
||||
* @property name network name
|
||||
* @property protocolName network protocol name
|
||||
* @property id network id
|
||||
* @property blockchain blockchain
|
||||
* @property iconResId network icon id from resources
|
||||
* @property isMainNetwork flag that determines if the network is the main network for the token
|
||||
* @property isAdded flag that determines if the user has saved the network
|
||||
* @property address contract address
|
||||
* @property decimals decimal count
|
||||
* @property onToggleClick lambda be invoked when switch is been toggled
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class Toggleable(
|
||||
override val name: String,
|
||||
override val protocolName: String,
|
||||
override val id: String,
|
||||
override val blockchain: Blockchain,
|
||||
val iconResId: MutableState<Int>,
|
||||
val isMainNetwork: Boolean,
|
||||
val isAdded: MutableState<Boolean>,
|
||||
val address: String?,
|
||||
val decimals: Int?,
|
||||
val onToggleClick: (TokenItemState.Loaded, Toggleable) -> Unit,
|
||||
) : NetworkItemState {
|
||||
|
||||
/**
|
||||
* Change toggle state [isAdded].
|
||||
*
|
||||
* It is a hack that helps us to change element of flow
|
||||
*/
|
||||
fun changeToggleState() {
|
||||
val reverseState = !isAdded.value
|
||||
isAdded.value = reverseState
|
||||
iconResId.value = if (reverseState) getActiveIconRes(blockchain.id) else getGreyedOutIconRes(blockchain.id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Network item state that can be selected
|
||||
*
|
||||
* @property name network name
|
||||
* @property protocolName network protocol name
|
||||
* @property iconResId network icon id from resources
|
||||
* @property id network id
|
||||
* @property blockchain blockchain
|
||||
* @property onNetworkClick lambda be invoked when network item is been clicked
|
||||
*
|
||||
*/
|
||||
class Selectable(
|
||||
override val name: String,
|
||||
override val protocolName: String,
|
||||
val iconResId: Int,
|
||||
override val id: String,
|
||||
override val blockchain: Blockchain,
|
||||
val onNetworkClick: (NetworkItemState) -> Unit,
|
||||
) : NetworkItemState
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.managetokens.presentation.common.state
|
||||
|
||||
internal data class WalletState(
|
||||
val walletId: String,
|
||||
val artworkUrl: String?,
|
||||
val walletName: String,
|
||||
val onSelected: (String) -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.managetokens.presentation.common.state.previewdata
|
||||
|
||||
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
|
||||
import com.tangem.managetokens.presentation.common.state.WalletState
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal object ChooseWalletStatePreviewData {
|
||||
|
||||
val state: ChooseWalletState.Choose
|
||||
get() = ChooseWalletState.Choose(
|
||||
wallets = persistentListOf(
|
||||
walletState,
|
||||
walletState.copy(walletId = "2"),
|
||||
),
|
||||
selectedWallet = walletState,
|
||||
onChooseWalletClick = {},
|
||||
onCloseChoosingWalletClick = {},
|
||||
)
|
||||
|
||||
private val walletState: WalletState
|
||||
get() = WalletState(
|
||||
walletName = "My wallet",
|
||||
walletId = "1",
|
||||
artworkUrl = "",
|
||||
onSelected = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
package com.tangem.managetokens.presentation.common.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
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 coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
|
||||
import com.tangem.managetokens.presentation.common.state.WalletState
|
||||
import com.tangem.managetokens.presentation.common.state.previewdata.ChooseWalletStatePreviewData
|
||||
|
||||
@Composable
|
||||
internal fun ChooseWalletScreen(state: ChooseWalletState.Choose, modifier: Modifier = Modifier) {
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size44),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = state.onCloseChoosingWalletClick,
|
||||
modifier = Modifier.align(Alignment.CenterStart),
|
||||
) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_back_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = R.string.manage_tokens_wallet_selector_title),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.Center),
|
||||
)
|
||||
}
|
||||
}
|
||||
items(
|
||||
count = state.wallets.count(),
|
||||
key = { index -> state.wallets[index].walletId },
|
||||
) { index ->
|
||||
WalletItem(
|
||||
wallet = state.wallets[index],
|
||||
selectedWallet = state.selectedWallet,
|
||||
modifier = Modifier
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = state.wallets.lastIndex,
|
||||
addDefaultPadding = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
item {
|
||||
SpacerH(height = TangemTheme.dimens.spacing16)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletItem(wallet: WalletState, selectedWallet: WalletState?, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clickable { wallet.onSelected(wallet.walletId) }
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size72)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier.size(height = TangemTheme.dimens.size30, width = TangemTheme.dimens.size50),
|
||||
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(wallet.artworkUrl)
|
||||
.crossfade(enable = true)
|
||||
.build(),
|
||||
loading = {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.card_placeholder_primary),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
error = {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.card_placeholder_primary),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
SpacerW12()
|
||||
Text(
|
||||
text = wallet.walletName,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
SpacerWMax()
|
||||
if (selectedWallet == wallet) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_check_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseWalletScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseWalletScreen(
|
||||
state = ChooseWalletStatePreviewData.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseWalletScreen_Dark() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseWalletScreen(
|
||||
state = ChooseWalletStatePreviewData.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
package com.tangem.managetokens.presentation.common.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.NetworkItemState
|
||||
import com.tangem.managetokens.presentation.managetokens.state.TokenItemState
|
||||
import com.tangem.managetokens.presentation.managetokens.state.previewdata.TokenItemStatePreviewData
|
||||
|
||||
@Composable
|
||||
internal fun NetworkItem(state: NetworkItemState, tokenState: TokenItemState.Loaded?, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.defaultMinSize(minHeight = TangemTheme.dimens.size68)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
NetworkIcon(model = state)
|
||||
SpacerW(width = TangemTheme.dimens.spacing12)
|
||||
Text(
|
||||
text = state.name,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
SpacerW(width = TangemTheme.dimens.spacing6)
|
||||
Text(
|
||||
text = state.protocolName,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
)
|
||||
if (state is NetworkItemState.Toggleable) {
|
||||
TangemSwitch(
|
||||
onCheckedChange = {
|
||||
state.onToggleClick(tokenState!!, state)
|
||||
},
|
||||
checked = state.isAdded.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun NetworkIcon(model: NetworkItemState, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier.size(size = TangemTheme.dimens.size36)) {
|
||||
val isAdded = when (model) {
|
||||
is NetworkItemState.Selectable -> true
|
||||
is NetworkItemState.Toggleable -> model.isAdded.value
|
||||
}
|
||||
|
||||
if (!isAdded) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.clip(CircleShape)
|
||||
.background(TangemTheme.colors.control.unchecked),
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
painter = painterResource(id = model.iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(size = TangemTheme.dimens.size36),
|
||||
tint = if (isAdded) Color.Unspecified else TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
||||
if (model is NetworkItemState.Toggleable && model.isMainNetwork) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.size(TangemTheme.dimens.size10)
|
||||
.clip(CircleShape)
|
||||
.background(TangemTheme.colors.stroke.transparency),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size8)
|
||||
.clip(CircleShape)
|
||||
.background(TangemTheme.colors.icon.accent),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_NetworkItem_Light(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) {
|
||||
TangemTheme(isDark = false) {
|
||||
NetworkItem(state, tokenState = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_NetworkItem_Dark(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) {
|
||||
TangemTheme(isDark = true) {
|
||||
NetworkItem(state, tokenState = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded)
|
||||
}
|
||||
}
|
||||
|
||||
private class NetworkItemStateProvider : CollectionPreviewParameterProvider<NetworkItemState>(
|
||||
collection = listOf(
|
||||
NetworkItemState.Toggleable(
|
||||
name = "Ethereum",
|
||||
protocolName = "ETH",
|
||||
iconResId = mutableStateOf(R.drawable.img_polygon_22),
|
||||
isMainNetwork = true,
|
||||
isAdded = mutableStateOf(true),
|
||||
id = "",
|
||||
address = "",
|
||||
onToggleClick = { _, _ -> },
|
||||
blockchain = Blockchain.Ethereum,
|
||||
decimals = 0,
|
||||
),
|
||||
NetworkItemState.Toggleable(
|
||||
name = "BNB SMART CHAIN",
|
||||
protocolName = "BEP20",
|
||||
iconResId = mutableStateOf(R.drawable.ic_bsc_16),
|
||||
isMainNetwork = false,
|
||||
isAdded = mutableStateOf(false),
|
||||
id = "",
|
||||
address = "",
|
||||
onToggleClick = { _, _ -> },
|
||||
blockchain = Blockchain.BSC,
|
||||
decimals = 0,
|
||||
),
|
||||
NetworkItemState.Selectable(
|
||||
name = "Ethereum",
|
||||
protocolName = "ETH",
|
||||
iconResId = R.drawable.img_polygon_22,
|
||||
id = "",
|
||||
onNetworkClick = { },
|
||||
blockchain = Blockchain.Ethereum,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.managetokens.presentation.common.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun SimpleSelectionBlock(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
roundedCorners: Boolean = true,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.then(
|
||||
if (roundedCorners) {
|
||||
Modifier.clip(shape = RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.background(color = TangemTheme.colors.background.action)
|
||||
.clickable { onClick() }
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing20,
|
||||
vertical = TangemTheme.dimens.spacing16,
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
SpacerH(height = TangemTheme.dimens.spacing4)
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_SimpleSelectionBlock_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
SimpleSelectionBlock(title = "Wallet", subtitle = "Family Wallet", onClick = { })
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_SimpleSelectionBlock_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
SimpleSelectionBlock(title = "Wallet", subtitle = "Family Wallet", onClick = { })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.state
|
||||
|
||||
import com.tangem.managetokens.presentation.common.state.NetworkItemState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class ChooseNetworkState(
|
||||
val nativeNetworks: ImmutableList<NetworkItemState>,
|
||||
val nonNativeNetworks: ImmutableList<NetworkItemState>,
|
||||
val onNonNativeNetworkHintClick: () -> Unit,
|
||||
val onCloseChooseNetworkScreen: () -> Unit,
|
||||
)
|
||||
|
|
@ -8,7 +8,8 @@ import com.tangem.features.managetokens.impl.R
|
|||
|
||||
data class DerivationNotificationState(
|
||||
val totalNeeded: Int,
|
||||
val missingAddressesCount: Int,
|
||||
val totalWallets: Int,
|
||||
val walletsToDerive: Int,
|
||||
val onGenerateClick: () -> Unit,
|
||||
) {
|
||||
val config = NotificationConfig(
|
||||
|
|
@ -25,8 +26,8 @@ data class DerivationNotificationState(
|
|||
onClick = onGenerateClick,
|
||||
additionalText = pluralReference(
|
||||
id = R.plurals.manage_tokens_number_of_wallets_android,
|
||||
count = totalNeeded,
|
||||
formatArgs = wrappedList(missingAddressesCount, totalNeeded),
|
||||
count = totalWallets,
|
||||
formatArgs = wrappedList(walletsToDerive, totalWallets),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.state
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
|
||||
internal sealed class TokenItemState {
|
||||
|
||||
abstract val id: String
|
||||
|
|
@ -9,11 +11,13 @@ internal sealed class TokenItemState {
|
|||
data class Loaded(
|
||||
override val id: String,
|
||||
val name: String,
|
||||
val currencyId: String,
|
||||
val currencySymbol: String,
|
||||
val tokenId: String,
|
||||
val tokenIcon: TokenIconState,
|
||||
val quotes: QuotesState,
|
||||
val rate: String?,
|
||||
val availableAction: TokenButtonType,
|
||||
val onButtonClick: (String) -> Unit,
|
||||
val availableAction: MutableState<TokenButtonType>,
|
||||
val chooseNetworkState: ChooseNetworkState,
|
||||
val onButtonClick: (Loaded) -> Unit,
|
||||
) : TokenItemState()
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.state.previewdata
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.NetworkItemState
|
||||
import com.tangem.managetokens.presentation.managetokens.state.ChooseNetworkState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal object ChooseNetworkStatePreviewData {
|
||||
|
||||
val state = ChooseNetworkState(
|
||||
nativeNetworks = nativeNetworks.toImmutableList(),
|
||||
nonNativeNetworks = nonNativeNetworks.toImmutableList(),
|
||||
onNonNativeNetworkHintClick = {},
|
||||
onCloseChooseNetworkScreen = {},
|
||||
)
|
||||
}
|
||||
|
||||
internal val nativeNetworks = listOf(
|
||||
NetworkItemState.Toggleable(
|
||||
name = "Ethereum",
|
||||
protocolName = "ETH",
|
||||
iconResId = mutableStateOf(R.drawable.img_polygon_22),
|
||||
isMainNetwork = true,
|
||||
isAdded = mutableStateOf(true),
|
||||
id = "",
|
||||
onToggleClick = { _, _ -> },
|
||||
blockchain = Blockchain.Ethereum,
|
||||
address = "",
|
||||
decimals = 0,
|
||||
),
|
||||
)
|
||||
|
||||
internal val nonNativeNetworks = listOf(
|
||||
NetworkItemState.Toggleable(
|
||||
name = "Ethereum",
|
||||
protocolName = "ETH",
|
||||
iconResId = mutableStateOf(R.drawable.img_kusama_22),
|
||||
isMainNetwork = false,
|
||||
isAdded = mutableStateOf(true),
|
||||
id = "",
|
||||
onToggleClick = { _, _ -> },
|
||||
blockchain = Blockchain.Ethereum,
|
||||
address = "",
|
||||
decimals = 0,
|
||||
),
|
||||
NetworkItemState.Toggleable(
|
||||
name = "BNB SMART CHAIN",
|
||||
protocolName = "BEP20",
|
||||
iconResId = mutableStateOf(R.drawable.ic_bsc_16),
|
||||
isMainNetwork = false,
|
||||
isAdded = mutableStateOf(false),
|
||||
id = "",
|
||||
onToggleClick = { _, _ -> },
|
||||
blockchain = Blockchain.BSC,
|
||||
address = "",
|
||||
decimals = 0,
|
||||
),
|
||||
)
|
||||
|
|
@ -4,8 +4,9 @@ import com.tangem.managetokens.presentation.managetokens.state.DerivationNotific
|
|||
|
||||
object DerivationNotificationStatePreviewData {
|
||||
val state = DerivationNotificationState(
|
||||
totalNeeded = 3,
|
||||
missingAddressesCount = 2,
|
||||
totalNeeded = 5,
|
||||
totalWallets = 3,
|
||||
walletsToDerive = 2,
|
||||
onGenerateClick = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.state.previewdata
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.managetokens.presentation.managetokens.state.*
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -13,7 +14,8 @@ internal object TokenItemStatePreviewData {
|
|||
get() = TokenItemState.Loaded(
|
||||
id = "BTC",
|
||||
name = "Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin",
|
||||
currencyId = "BTC",
|
||||
tokenId = "BTC",
|
||||
currencySymbol = "BTC",
|
||||
tokenIcon = tokenIconState,
|
||||
quotes = QuotesState.Content(
|
||||
priceChange = "0.43%",
|
||||
|
|
@ -21,15 +23,17 @@ internal object TokenItemStatePreviewData {
|
|||
chartData = persistentListOf(10f, 2f, 5f, 3f, 4f, 8f, 9f, 7f, 4f),
|
||||
),
|
||||
rate = "31 285.72$",
|
||||
availableAction = TokenButtonType.ADD,
|
||||
availableAction = mutableStateOf(TokenButtonType.ADD),
|
||||
onButtonClick = {},
|
||||
chooseNetworkState = ChooseNetworkStatePreviewData.state,
|
||||
)
|
||||
|
||||
val loadedPriceUp: TokenItemState
|
||||
get() = TokenItemState.Loaded(
|
||||
id = "BTC",
|
||||
name = "Bitcoin",
|
||||
currencyId = "BTC",
|
||||
tokenId = "BTC",
|
||||
currencySymbol = "BTC",
|
||||
tokenIcon = tokenIconState,
|
||||
quotes = QuotesState.Content(
|
||||
priceChange = "0.43%",
|
||||
|
|
@ -37,8 +41,9 @@ internal object TokenItemStatePreviewData {
|
|||
chartData = persistentListOf(1f, 3f, 4f, 8f, 12f, 10f, 8f, 3f, 5f, 7f),
|
||||
),
|
||||
rate = "31 285.72$",
|
||||
availableAction = TokenButtonType.NOT_AVAILABLE,
|
||||
availableAction = mutableStateOf(TokenButtonType.NOT_AVAILABLE),
|
||||
onButtonClick = {},
|
||||
chooseNetworkState = ChooseNetworkStatePreviewData.state,
|
||||
)
|
||||
|
||||
private val tokenIconState: TokenIconState
|
||||
|
|
|
|||
|
|
@ -0,0 +1,205 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.WarningCardTitleOnly
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
|
||||
import com.tangem.managetokens.presentation.common.state.previewdata.ChooseWalletStatePreviewData
|
||||
import com.tangem.managetokens.presentation.common.ui.components.NetworkItem
|
||||
import com.tangem.managetokens.presentation.common.ui.components.SimpleSelectionBlock
|
||||
import com.tangem.managetokens.presentation.managetokens.state.ChooseNetworkState
|
||||
import com.tangem.managetokens.presentation.managetokens.state.TokenItemState
|
||||
import com.tangem.managetokens.presentation.managetokens.state.previewdata.TokenItemStatePreviewData
|
||||
|
||||
@Composable
|
||||
internal fun ChooseNetworkScreen(
|
||||
state: TokenItemState.Loaded,
|
||||
walletState: ChooseWalletState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val networkState = state.chooseNetworkState
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(id = R.string.manage_tokens_network_selector_title),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
when (walletState) {
|
||||
is ChooseWalletState.Choose -> {
|
||||
item {
|
||||
SpacerH(height = TangemTheme.dimens.spacing10)
|
||||
}
|
||||
item {
|
||||
SimpleSelectionBlock(
|
||||
title = stringResource(id = R.string.manage_tokens_network_selector_wallet),
|
||||
subtitle = walletState.selectedWallet?.walletName ?: "",
|
||||
onClick = walletState.onChooseWalletClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
ChooseWalletState.NoSelection -> Unit
|
||||
is ChooseWalletState.Warning -> {
|
||||
item {
|
||||
SpacerH(height = TangemTheme.dimens.spacing10)
|
||||
}
|
||||
item {
|
||||
WarningCardTitleOnly(
|
||||
title = stringResource(id = R.string.manage_tokens_wallet_support_only_one_network_title),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
SpacerH(height = TangemTheme.dimens.spacing16)
|
||||
}
|
||||
|
||||
item {
|
||||
if (networkState.nativeNetworks.isNotEmpty()) {
|
||||
NativeNetworks(networkState = networkState, tokenState = state)
|
||||
}
|
||||
}
|
||||
|
||||
if (networkState.nonNativeNetworks.isNotEmpty()) {
|
||||
item {
|
||||
NonNativeNetworksHeader(networkState.onNonNativeNetworkHintClick)
|
||||
}
|
||||
item {
|
||||
SpacerH(height = TangemTheme.dimens.spacing8)
|
||||
}
|
||||
item {
|
||||
this@LazyColumn.NonNativeNetworks(networkState = networkState, tokenState = state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NativeNetworks(networkState: ChooseNetworkState, tokenState: TokenItemState.Loaded) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(id = R.string.manage_tokens_network_selector_native_title),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption1,
|
||||
)
|
||||
SpacerH(height = TangemTheme.dimens.spacing2)
|
||||
Text(
|
||||
text = stringResource(id = R.string.manage_tokens_network_selector_native_subtitle),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
SpacerH(height = TangemTheme.dimens.spacing8)
|
||||
|
||||
networkState.nativeNetworks.forEachIndexed { index, network ->
|
||||
NetworkItem(
|
||||
state = network,
|
||||
tokenState = tokenState,
|
||||
modifier = Modifier
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = networkState.nativeNetworks.lastIndex,
|
||||
addDefaultPadding = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
SpacerH(height = TangemTheme.dimens.spacing16)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyListScope.NonNativeNetworks(networkState: ChooseNetworkState, tokenState: TokenItemState.Loaded) {
|
||||
items(
|
||||
count = networkState.nonNativeNetworks.count(),
|
||||
key = { index -> networkState.nonNativeNetworks[index].id },
|
||||
) { index ->
|
||||
NetworkItem(
|
||||
state = networkState.nonNativeNetworks[index],
|
||||
tokenState = tokenState,
|
||||
modifier = Modifier
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = networkState.nonNativeNetworks.lastIndex,
|
||||
addDefaultPadding = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
item {
|
||||
SpacerH(height = TangemTheme.dimens.spacing16)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NonNativeNetworksHeader(onNonNativeNetworkHintClick: () -> Unit) {
|
||||
Column {
|
||||
Row {
|
||||
Text(
|
||||
text = stringResource(id = R.string.manage_tokens_network_selector_non_native_title),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption1,
|
||||
)
|
||||
SpacerW(width = TangemTheme.dimens.spacing2)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_information_24),
|
||||
tint = TangemTheme.colors.icon.inactive,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size16)
|
||||
.clickable { onNonNativeNetworkHintClick() },
|
||||
)
|
||||
}
|
||||
SpacerH(height = TangemTheme.dimens.spacing2)
|
||||
Text(
|
||||
text = stringResource(id = R.string.manage_tokens_network_selector_non_native_subtitle),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseNetworkScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseNetworkScreen(
|
||||
state = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded,
|
||||
walletState = ChooseWalletStatePreviewData.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseNetworkScreen_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
ChooseNetworkScreen(
|
||||
state = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded,
|
||||
walletState = ChooseWalletStatePreviewData.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ private fun LoadedTokenItem(state: TokenItemState.Loaded, modifier: Modifier = M
|
|||
modifier = Modifier
|
||||
.weight(weight = 1f),
|
||||
) {
|
||||
TokenName(name = state.name, currencyId = state.currencyId)
|
||||
TokenName(name = state.name, currencyId = state.currencySymbol)
|
||||
TokenPriceData(price = state.rate, quotesState = state.quotes)
|
||||
}
|
||||
SpacerW24()
|
||||
|
|
@ -67,8 +67,8 @@ private fun LoadedTokenItem(state: TokenItemState.Loaded, modifier: Modifier = M
|
|||
}
|
||||
|
||||
TokenButton(
|
||||
type = state.availableAction,
|
||||
onClick = { state.onButtonClick(state.currencyId) },
|
||||
type = state.availableAction.value,
|
||||
onClick = { state.onButtonClick(state) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ dependencies {
|
|||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.navigation)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.models)
|
||||
|
|
@ -58,6 +59,8 @@ dependencies {
|
|||
implementation(projects.domain.txhistory)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.demo)
|
||||
|
||||
/** Feature modules */
|
||||
implementation(projects.features.send.api)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.di
|
||||
|
||||
import com.tangem.core.navigation.ReduxNavController
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.send.impl.navigation.DefaultSendRouter
|
||||
import dagger.Module
|
||||
|
|
@ -17,7 +18,7 @@ internal object SendRouterModule {
|
|||
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
fun provideSendRouter(): SendRouter {
|
||||
return DefaultSendRouter()
|
||||
fun provideSendRouter(reduxNavController: ReduxNavController): SendRouter {
|
||||
return DefaultSendRouter(reduxNavController)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,17 @@
|
|||
package com.tangem.features.send.impl.navigation
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.navigation.ReduxNavController
|
||||
import com.tangem.features.send.impl.presentation.SendFragment
|
||||
|
||||
internal class DefaultSendRouter : SendRouter {
|
||||
internal class DefaultSendRouter(
|
||||
private val reduxNavController: ReduxNavController,
|
||||
) : InnerSendRouter {
|
||||
|
||||
override fun getEntryFragment(): Fragment = SendFragment.create()
|
||||
|
||||
override fun openUrl(url: String) {
|
||||
reduxNavController.navigate(NavigationAction.OpenUrl(url = url))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.send.impl.navigation
|
||||
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
|
||||
interface InnerSendRouter : SendRouter {
|
||||
|
||||
/** Open website by [url] */
|
||||
fun openUrl(url: String)
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ import com.tangem.core.ui.components.SystemBarsEffect
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.send.impl.navigation.InnerSendRouter
|
||||
import com.tangem.features.send.impl.presentation.state.StateRouter
|
||||
import com.tangem.features.send.impl.presentation.ui.SendScreen
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendViewModel
|
||||
|
|
@ -24,12 +26,20 @@ internal class SendFragment : ComposeFragment() {
|
|||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
lateinit var router: SendRouter
|
||||
|
||||
private val viewModel by viewModels<SendViewModel>()
|
||||
private val innerSendRouter: InnerSendRouter
|
||||
get() = requireNotNull(router as? InnerSendRouter) {
|
||||
"innerSendRouter should be instance of InnerSendRouter"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycle.addObserver(viewModel)
|
||||
viewModel.setRouter(
|
||||
innerSendRouter,
|
||||
StateRouter(
|
||||
fragmentManager = WeakReference(parentFragmentManager),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.send.impl.presentation.domain
|
||||
|
||||
sealed class SendNotification {
|
||||
|
||||
sealed class Info(val message: String) : SendNotification()
|
||||
|
||||
sealed class Critical(val message: String) : SendNotification()
|
||||
|
||||
sealed class Error(val message: String) : SendNotification()
|
||||
}
|
||||
|
|
@ -86,6 +86,7 @@ internal class SendStateFactory(
|
|||
amountState = amountStateConverter.convert(Unit),
|
||||
recipientState = recipientStateConverter.convert(Unit),
|
||||
feeState = feeStateConverter.convert(Unit),
|
||||
sendState = SendStates.SendState(),
|
||||
)
|
||||
//endregion
|
||||
|
||||
|
|
@ -122,15 +123,15 @@ internal class SendStateFactory(
|
|||
val recipientState = state.recipientState ?: return state
|
||||
|
||||
val isValidMemo = validateMemo(
|
||||
memo = value,
|
||||
memo = recipientState.addressTextField.value.value,
|
||||
cryptoCurrency = cryptoCurrencyStatusProvider().currency,
|
||||
)
|
||||
val isAddressInWallet = isNotAddressInWallet(
|
||||
address = value,
|
||||
walletAddresses = walletAddressesProvider(),
|
||||
address = recipientState.addressTextField.value.value,
|
||||
)
|
||||
val isValidAddress = verifyAddress(
|
||||
address = recipientState.addressTextField.value.value,
|
||||
address = value,
|
||||
cryptoCurrency = cryptoCurrencyStatusProvider().currency,
|
||||
)
|
||||
|
||||
|
|
@ -138,8 +139,7 @@ internal class SendStateFactory(
|
|||
it.copy(
|
||||
value = value,
|
||||
error = when {
|
||||
!isValidAddress -> TextReference.Res(R.string.send_recipient_address_error)
|
||||
!isAddressInWallet -> TextReference.Res(R.string.send_recipient_address_error)
|
||||
!isValidAddress || !isAddressInWallet -> TextReference.Res(R.string.send_recipient_address_error)
|
||||
else -> null
|
||||
},
|
||||
isError = !isValidAddress || !isAddressInWallet,
|
||||
|
|
@ -169,11 +169,9 @@ internal class SendStateFactory(
|
|||
cryptoCurrency = cryptoCurrencyStatusProvider().currency,
|
||||
)
|
||||
|
||||
// todo add memo validation error text
|
||||
recipientState.memoTextField?.update {
|
||||
it.copy(
|
||||
value = value,
|
||||
error = TextReference.Res(R.string.send_memo_destination_tag_error),
|
||||
isError = !isValidMemo,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ internal data class SendUiState(
|
|||
val amountState: SendStates.AmountState? = null,
|
||||
val recipientState: SendStates.RecipientState? = null,
|
||||
val feeState: SendStates.FeeState? = null,
|
||||
val sendState: SendStates.SendState? = null,
|
||||
val recipientList: MutableStateFlow<PagingData<SendRecipientListContent>> = MutableStateFlow(PagingData.empty()),
|
||||
val currentState: MutableStateFlow<SendUiStateType>,
|
||||
)
|
||||
|
|
@ -65,11 +66,14 @@ internal sealed class SendStates {
|
|||
val receivedAmount: MutableStateFlow<String> = MutableStateFlow(""),
|
||||
) : SendStates()
|
||||
|
||||
// todo [REDACTED_JIRA]
|
||||
/** Send state */
|
||||
data class SendState(
|
||||
val isSuccess: Boolean,
|
||||
)
|
||||
override val type: SendUiStateType = SendUiStateType.Send,
|
||||
val isSending: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val isSuccess: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val transactionDate: MutableStateFlow<Long> = MutableStateFlow(0L),
|
||||
val txUrl: MutableStateFlow<String> = MutableStateFlow(""),
|
||||
) : SendStates()
|
||||
}
|
||||
|
||||
enum class SendUiStateType {
|
||||
|
|
@ -77,5 +81,4 @@ enum class SendUiStateType {
|
|||
Recipient,
|
||||
Fee,
|
||||
Send,
|
||||
Done,
|
||||
}
|
||||
|
|
@ -9,28 +9,61 @@ internal class StateRouter(
|
|||
private val fragmentManager: WeakReference<FragmentManager>,
|
||||
) {
|
||||
var currentState: MutableStateFlow<SendUiStateType> = MutableStateFlow(SendUiStateType.Amount)
|
||||
private set
|
||||
|
||||
private var isFromSend: Boolean = false
|
||||
|
||||
fun popBackStack() {
|
||||
fragmentManager.get()?.popBackStack()
|
||||
}
|
||||
|
||||
fun onBackClick() {
|
||||
fragmentManager.get()?.popBackStack()
|
||||
if (isFromSend) {
|
||||
showSend()
|
||||
} else {
|
||||
when (currentState.value) {
|
||||
SendUiStateType.Amount -> popBackStack()
|
||||
SendUiStateType.Recipient -> showAmount()
|
||||
SendUiStateType.Fee -> showRecipient()
|
||||
SendUiStateType.Send -> showFee()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onNextClick() {
|
||||
when (currentState.value) {
|
||||
SendUiStateType.Amount -> currentState.update { SendUiStateType.Recipient }
|
||||
SendUiStateType.Recipient -> currentState.update { SendUiStateType.Fee }
|
||||
SendUiStateType.Fee -> currentState.update { SendUiStateType.Send }
|
||||
SendUiStateType.Send -> currentState.update { SendUiStateType.Done }
|
||||
SendUiStateType.Done -> onBackClick()
|
||||
SendUiStateType.Amount -> showRecipient()
|
||||
SendUiStateType.Recipient -> showFee()
|
||||
SendUiStateType.Fee -> showSend()
|
||||
SendUiStateType.Send -> onBackClick()
|
||||
}
|
||||
}
|
||||
|
||||
fun onPrevClick() {
|
||||
when (currentState.value) {
|
||||
SendUiStateType.Amount -> onBackClick()
|
||||
SendUiStateType.Recipient -> currentState.update { SendUiStateType.Amount }
|
||||
SendUiStateType.Fee -> currentState.update { SendUiStateType.Recipient }
|
||||
SendUiStateType.Send -> currentState.update { SendUiStateType.Fee }
|
||||
SendUiStateType.Done -> onBackClick()
|
||||
SendUiStateType.Amount -> popBackStack()
|
||||
SendUiStateType.Recipient -> showAmount()
|
||||
SendUiStateType.Fee -> showRecipient()
|
||||
SendUiStateType.Send -> popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
fun showAmount(isFromSend: Boolean = false) {
|
||||
this.isFromSend = isFromSend
|
||||
currentState.update { SendUiStateType.Amount }
|
||||
}
|
||||
|
||||
fun showRecipient(isFromSend: Boolean = false) {
|
||||
this.isFromSend = isFromSend
|
||||
currentState.update { SendUiStateType.Recipient }
|
||||
}
|
||||
|
||||
fun showFee(isFromSend: Boolean = false) {
|
||||
this.isFromSend = isFromSend
|
||||
currentState.update { SendUiStateType.Fee }
|
||||
}
|
||||
|
||||
fun showSend() {
|
||||
currentState.update { SendUiStateType.Send }
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ internal class SendRecipientMemoFieldConverter(
|
|||
),
|
||||
placeholder = TextReference.Res(R.string.send_optional_field),
|
||||
label = TextReference.Res(value),
|
||||
error = TextReference.Res(R.string.send_memo_destination_tag_error),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,30 @@
|
|||
package com.tangem.features.send.impl.presentation.ui
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
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.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.shareText
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiStateType
|
||||
|
|
@ -64,16 +71,16 @@ private fun SendSecondaryNavigationButton(uiState: SendUiState) {
|
|||
|
||||
@Composable
|
||||
private fun SendPrimaryNavigationButton(uiState: SendUiState, modifier: Modifier = Modifier) {
|
||||
val currentState = uiState.currentState.collectAsState()
|
||||
val currentState = uiState.currentState.collectAsStateWithLifecycle()
|
||||
val isSuccess = uiState.sendState?.isSuccess?.collectAsStateWithLifecycle()?.value ?: false
|
||||
val isSending = uiState.sendState?.isSending?.collectAsStateWithLifecycle()?.value ?: false
|
||||
val txUrl = uiState.sendState?.txUrl?.collectAsStateWithLifecycle()?.value.orEmpty()
|
||||
|
||||
val buttonTextId = when (currentState.value) {
|
||||
SendUiStateType.Amount,
|
||||
SendUiStateType.Recipient,
|
||||
SendUiStateType.Fee,
|
||||
-> R.string.common_next
|
||||
SendUiStateType.Send -> R.string.common_send
|
||||
else -> R.string.common_close
|
||||
}
|
||||
val (buttonTextId, buttonClick) = getButtonData(
|
||||
currentState = currentState,
|
||||
isSuccess = isSuccess,
|
||||
uiState = uiState,
|
||||
)
|
||||
|
||||
val isButtonEnabled = when (currentState.value) {
|
||||
SendUiStateType.Amount -> uiState.amountState?.isPrimaryButtonEnabled ?: false
|
||||
|
|
@ -86,19 +93,92 @@ private fun SendPrimaryNavigationButton(uiState: SendUiState, modifier: Modifier
|
|||
label = "Update send screen state",
|
||||
modifier = modifier,
|
||||
) { textId ->
|
||||
if (currentState.value == SendUiStateType.Send) {
|
||||
PrimaryButtonIconEnd(
|
||||
text = stringResource(textId),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
enabled = isButtonEnabled,
|
||||
onClick = uiState.clickIntents::onNextClick,
|
||||
)
|
||||
} else {
|
||||
PrimaryButton(
|
||||
text = stringResource(textId),
|
||||
enabled = isButtonEnabled,
|
||||
onClick = uiState.clickIntents::onNextClick,
|
||||
)
|
||||
when {
|
||||
currentState.value == SendUiStateType.Send && !isSuccess -> {
|
||||
PrimaryButtonIconEnd(
|
||||
text = stringResource(textId),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
enabled = isButtonEnabled,
|
||||
onClick = buttonClick,
|
||||
showProgress = isSending,
|
||||
)
|
||||
}
|
||||
currentState.value == SendUiStateType.Send && isSuccess -> {
|
||||
PrimaryButtonsDone(
|
||||
textRes = textId,
|
||||
txUrl = txUrl,
|
||||
onExploreClick = { uiState.clickIntents.onExploreClick(txUrl) },
|
||||
onDoneClick = buttonClick,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
PrimaryButton(
|
||||
text = stringResource(textId),
|
||||
enabled = isButtonEnabled,
|
||||
onClick = buttonClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PrimaryButtonsDone(
|
||||
@StringRes textRes: Int,
|
||||
txUrl: String,
|
||||
onExploreClick: () -> Unit,
|
||||
onDoneClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val context = LocalContext.current
|
||||
|
||||
Column(modifier = modifier) {
|
||||
if (txUrl.isNotBlank()) {
|
||||
Row {
|
||||
SecondaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_explore),
|
||||
iconResId = R.drawable.ic_web_24,
|
||||
onClick = onExploreClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
SpacerW12()
|
||||
SecondaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_share),
|
||||
iconResId = R.drawable.ic_share_24,
|
||||
onClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
context.shareText(txUrl)
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
SpacerH12()
|
||||
}
|
||||
PrimaryButton(
|
||||
text = stringResource(id = textRes),
|
||||
enabled = true,
|
||||
onClick = onDoneClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getButtonData(
|
||||
uiState: SendUiState,
|
||||
currentState: State<SendUiStateType>,
|
||||
isSuccess: Boolean,
|
||||
): Pair<Int, () -> Unit> {
|
||||
return when (currentState.value) {
|
||||
SendUiStateType.Amount,
|
||||
SendUiStateType.Recipient,
|
||||
SendUiStateType.Fee,
|
||||
-> R.string.common_next to uiState.clickIntents::onNextClick
|
||||
SendUiStateType.Send -> if (isSuccess) {
|
||||
R.string.common_close
|
||||
} else {
|
||||
R.string.common_send
|
||||
} to uiState.clickIntents::onSendClick
|
||||
}
|
||||
}
|
||||
|
|
@ -22,11 +22,13 @@ import com.tangem.features.send.impl.presentation.state.SendUiStateType
|
|||
import com.tangem.features.send.impl.presentation.ui.amount.SendAmountContent
|
||||
import com.tangem.features.send.impl.presentation.ui.fee.SendSpeedAndFeeContent
|
||||
import com.tangem.features.send.impl.presentation.ui.recipient.SendRecipientContent
|
||||
import com.tangem.features.send.impl.presentation.ui.send.SendContent
|
||||
|
||||
@Composable
|
||||
internal fun SendScreen(uiState: SendUiState) {
|
||||
val currentState = uiState.currentState.collectAsStateWithLifecycle()
|
||||
BackHandler { uiState.clickIntents.onPrevClick() }
|
||||
val isSuccess = uiState.sendState?.isSuccess?.collectAsStateWithLifecycle()
|
||||
BackHandler { uiState.clickIntents.onBackClick() }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -36,12 +38,10 @@ internal fun SendScreen(uiState: SendUiState) {
|
|||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
val titleRes = when (currentState.value) {
|
||||
SendUiStateType.Amount,
|
||||
SendUiStateType.Send,
|
||||
-> R.string.common_send
|
||||
SendUiStateType.Amount -> R.string.common_send
|
||||
SendUiStateType.Recipient -> R.string.send_recipient
|
||||
SendUiStateType.Fee -> R.string.common_fee_selector_title
|
||||
SendUiStateType.Done -> null
|
||||
SendUiStateType.Send -> if (isSuccess?.value == false) R.string.common_send else null
|
||||
}
|
||||
val iconRes = when (currentState.value) {
|
||||
SendUiStateType.Amount,
|
||||
|
|
@ -52,7 +52,7 @@ internal fun SendScreen(uiState: SendUiState) {
|
|||
|
||||
AppBarWithBackButtonAndIcon(
|
||||
text = titleRes?.let { stringResource(it) },
|
||||
onBackClick = uiState.clickIntents::onBackClick,
|
||||
onBackClick = uiState.clickIntents::popBackStack,
|
||||
onIconClick = uiState.clickIntents::onQrCodeScanClick,
|
||||
backIconRes = R.drawable.ic_close_24,
|
||||
iconRes = iconRes,
|
||||
|
|
@ -94,7 +94,7 @@ private fun SendScreenContent(
|
|||
uiState.feeState,
|
||||
uiState.clickIntents,
|
||||
)
|
||||
else -> { /* [REDACTED_TODO_COMMENT]*/ }
|
||||
SendUiStateType.Send -> SendContent(uiState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.tangem.core.ui.components.fields.AmountVisualTransformation
|
||||
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.fee
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.ui.components.fields.AmountVisualTransformation
|
||||
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
|
||||
import com.tangem.core.ui.components.inputrow.InputRowEnter
|
||||
import com.tangem.core.ui.components.inputrow.InputRowEnterInfo
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
import com.tangem.features.send.impl.presentation.ui.recipient.TextFieldWithInfo
|
||||
import com.tangem.features.send.impl.presentation.ui.common.FooterContainer
|
||||
|
||||
private const val ETHEREUM_UNIT = "GWEI"
|
||||
|
||||
|
|
@ -22,42 +26,66 @@ internal fun SendCustomFeeEthereum(
|
|||
symbol: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val fee = customValues.value[0]
|
||||
val gasPrice = customValues.value[1]
|
||||
val gasLimit = customValues.value[2]
|
||||
|
||||
if (selectedFee == FeeType.CUSTOM && customValues.value.isNotEmpty()) {
|
||||
val fee = customValues.value[0]
|
||||
val gasPrice = customValues.value[1]
|
||||
val gasLimit = customValues.value[2]
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
modifier = modifier,
|
||||
) {
|
||||
TextFieldWithInfo(
|
||||
value = fee.value,
|
||||
label = stringResource(R.string.send_max_fee),
|
||||
FooterContainer(
|
||||
footer = stringResource(R.string.send_max_fee_footer),
|
||||
info = fee.label,
|
||||
visualTransformation = AmountVisualTransformation(symbol),
|
||||
keyboardOptions = fee.keyboardOptions,
|
||||
onValueChange = fee.onValueChange,
|
||||
isSingleLine = true,
|
||||
)
|
||||
TextFieldWithInfo(
|
||||
value = gasPrice.value,
|
||||
label = stringResource(R.string.send_gas_price),
|
||||
) {
|
||||
InputRowEnterInfo(
|
||||
text = fee.value,
|
||||
title = TextReference.Res(R.string.send_max_fee),
|
||||
info = fee.label,
|
||||
visualTransformation = AmountVisualTransformation(symbol),
|
||||
keyboardOptions = fee.keyboardOptions,
|
||||
onValueChange = fee.onValueChange,
|
||||
isSingleLine = true,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
)
|
||||
}
|
||||
FooterContainer(
|
||||
footer = stringResource(R.string.send_gas_price_footer),
|
||||
onValueChange = gasPrice.onValueChange,
|
||||
visualTransformation = AmountVisualTransformation(ETHEREUM_UNIT),
|
||||
keyboardOptions = fee.keyboardOptions,
|
||||
isSingleLine = true,
|
||||
)
|
||||
TextFieldWithInfo(
|
||||
value = gasLimit.value,
|
||||
label = stringResource(R.string.send_gas_limit),
|
||||
) {
|
||||
InputRowEnter(
|
||||
text = gasPrice.value,
|
||||
title = TextReference.Res(R.string.send_gas_price),
|
||||
onValueChange = gasPrice.onValueChange,
|
||||
visualTransformation = AmountVisualTransformation(ETHEREUM_UNIT),
|
||||
keyboardOptions = fee.keyboardOptions,
|
||||
isSingleLine = true,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
)
|
||||
}
|
||||
FooterContainer(
|
||||
footer = stringResource(R.string.send_gas_limit_footer),
|
||||
onValueChange = gasLimit.onValueChange,
|
||||
keyboardOptions = fee.keyboardOptions,
|
||||
isSingleLine = true,
|
||||
)
|
||||
) {
|
||||
InputRowEnter(
|
||||
text = gasLimit.value,
|
||||
title = TextReference.Res(R.string.send_gas_limit),
|
||||
onValueChange = gasLimit.onValueChange,
|
||||
keyboardOptions = fee.keyboardOptions,
|
||||
isSingleLine = true,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,11 +18,13 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.itemContentType
|
||||
import androidx.paging.compose.itemKey
|
||||
import com.tangem.core.ui.components.inputrow.InputRowRecipient
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.ui.common.FooterContainer
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
|
||||
private const val ADDRESS_FIELD_KEY = "ADDRESS_FIELD_KEY"
|
||||
|
|
@ -45,18 +47,26 @@ internal fun SendRecipientContent(
|
|||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
item(key = ADDRESS_FIELD_KEY) {
|
||||
TextFieldWithPasteAndIcon(
|
||||
value = address.value,
|
||||
label = address.label,
|
||||
placeholder = address.placeholder,
|
||||
FooterContainer(
|
||||
footer = stringResource(R.string.send_recipient_address_footer, uiState.network),
|
||||
onValueChange = address.onValueChange,
|
||||
onPasteClick = clickIntents::onRecipientAddressValueChange,
|
||||
singleLine = true,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing4),
|
||||
isError = address.isError,
|
||||
error = address.error,
|
||||
)
|
||||
) {
|
||||
InputRowRecipient(
|
||||
value = address.value,
|
||||
title = address.label,
|
||||
placeholder = address.placeholder,
|
||||
onValueChange = address.onValueChange,
|
||||
onPasteClick = clickIntents::onRecipientAddressValueChange,
|
||||
singleLine = true,
|
||||
isError = address.isError,
|
||||
error = address.error,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing4)
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
memo?.let { memoField ->
|
||||
item(key = MEMO_FIELD_KEY) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.recipient
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.fields.SimpleTextField
|
||||
import com.tangem.core.ui.components.inputrow.inner.PasteButton
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.ui.common.FooterContainer
|
||||
|
||||
@Composable
|
||||
internal fun TextFieldWithPaste(
|
||||
value: String,
|
||||
placeholder: TextReference,
|
||||
label: TextReference,
|
||||
onValueChange: (String) -> Unit,
|
||||
onPasteClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
footer: String? = null,
|
||||
error: TextReference? = null,
|
||||
isError: Boolean = false,
|
||||
) {
|
||||
val (title, color) = if (isError && error != null) {
|
||||
error to TangemTheme.colors.text.warning
|
||||
} else {
|
||||
label to TangemTheme.colors.text.secondary
|
||||
}
|
||||
FooterContainer(modifier, footer) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = color,
|
||||
)
|
||||
SimpleTextField(
|
||||
value = value,
|
||||
placeholder = placeholder,
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing6),
|
||||
)
|
||||
}
|
||||
PasteButton(
|
||||
isPasteButtonVisible = value.isBlank(),
|
||||
onClick = onPasteClick,
|
||||
modifier = Modifier
|
||||
.align(CenterVertically)
|
||||
.padding(end = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,385 +0,0 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.recipient
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.ui.common.FooterContainer
|
||||
|
||||
@Composable
|
||||
internal fun TextFieldWithPasteAndIcon(
|
||||
value: String,
|
||||
placeholder: TextReference,
|
||||
label: TextReference,
|
||||
onValueChange: (String) -> Unit,
|
||||
onPasteClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
footer: String? = null,
|
||||
singleLine: Boolean = false,
|
||||
error: TextReference? = null,
|
||||
isError: Boolean = false,
|
||||
) {
|
||||
val (title, color) = if (isError && error != null) {
|
||||
error to TangemTheme.colors.text.warning
|
||||
} else {
|
||||
label to TangemTheme.colors.text.secondary
|
||||
}
|
||||
FooterContainer(modifier, footer) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = color,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
)
|
||||
Row {
|
||||
IdentIcon(
|
||||
address = value,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius20))
|
||||
.size(TangemTheme.dimens.size40)
|
||||
.background(TangemTheme.colors.background.tertiary),
|
||||
)
|
||||
SimpleTextField(
|
||||
value = value,
|
||||
placeholder = placeholder,
|
||||
onValueChange = onValueChange,
|
||||
singleLine = singleLine,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
)
|
||||
.weight(1f)
|
||||
.align(CenterVertically),
|
||||
)
|
||||
PasteButton(
|
||||
isPasteButtonVisible = value.isBlank(),
|
||||
onClick = onPasteClick,
|
||||
modifier = Modifier
|
||||
.align(CenterVertically)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing4,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TextFieldWithPaste(
|
||||
value: String,
|
||||
placeholder: TextReference,
|
||||
label: TextReference,
|
||||
onValueChange: (String) -> Unit,
|
||||
onPasteClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
footer: String? = null,
|
||||
error: TextReference? = null,
|
||||
isError: Boolean = false,
|
||||
) {
|
||||
val (title, color) = if (isError && error != null) {
|
||||
error to TangemTheme.colors.text.warning
|
||||
} else {
|
||||
label to TangemTheme.colors.text.secondary
|
||||
}
|
||||
FooterContainer(modifier, footer) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = color,
|
||||
)
|
||||
SimpleTextField(
|
||||
value = value,
|
||||
placeholder = placeholder,
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing6),
|
||||
)
|
||||
}
|
||||
PasteButton(
|
||||
isPasteButtonVisible = value.isBlank(),
|
||||
onClick = onPasteClick,
|
||||
modifier = Modifier
|
||||
.align(CenterVertically)
|
||||
.padding(end = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TextFieldWithInfo(
|
||||
value: String,
|
||||
label: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
info: TextReference? = null,
|
||||
footer: String? = null,
|
||||
isSingleLine: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
) {
|
||||
FooterContainer(
|
||||
footer = footer,
|
||||
footerTopPadding = TangemTheme.dimens.spacing6,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing14,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Row {
|
||||
SimpleTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
visualTransformation = visualTransformation,
|
||||
singleLine = isSingleLine,
|
||||
keyboardOptions = keyboardOptions,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing6)
|
||||
.weight(1f),
|
||||
)
|
||||
info?.let {
|
||||
Text(
|
||||
text = it.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing8)
|
||||
.align(Alignment.Bottom),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PasteButton(isPasteButtonVisible: Boolean, onClick: (String) -> Unit, modifier: Modifier = Modifier) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
if (isPasteButtonVisible) {
|
||||
Box(modifier = modifier) {
|
||||
Text(
|
||||
text = "Paste",
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary2,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.button.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing10,
|
||||
vertical = TangemTheme.dimens.spacing2,
|
||||
)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(radius = TangemTheme.dimens.radius8),
|
||||
onClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onClick(
|
||||
clipboardManager
|
||||
.getText()
|
||||
?.toString()
|
||||
.orEmpty(),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = stringResource(R.string.common_close),
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(radius = TangemTheme.dimens.radius10),
|
||||
onClick = { onClick("") },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SimpleTextField(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
placeholder: TextReference? = null,
|
||||
singleLine: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
BasicTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
textStyle = TangemTheme.typography.body2.copy(color = TangemTheme.colors.text.primary1),
|
||||
cursorBrush = SolidColor(TangemTheme.colors.text.primary1),
|
||||
singleLine = singleLine,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
decorationBox = { textValue ->
|
||||
Box {
|
||||
if (value.isBlank() && placeholder != null) {
|
||||
Text(
|
||||
text = placeholder.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.disabled,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
textValue()
|
||||
}
|
||||
},
|
||||
modifier = modifier
|
||||
.focusRequester(focusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TextFieldPreview_Light() {
|
||||
TangemTheme {
|
||||
Column {
|
||||
TextFieldWithPaste(
|
||||
value = "",
|
||||
label = TextReference.Res(R.string.send_recipient),
|
||||
placeholder = TextReference.Res(R.string.send_enter_address_field),
|
||||
onValueChange = {},
|
||||
onPasteClick = {},
|
||||
)
|
||||
SpacerH8()
|
||||
TextFieldWithPasteAndIcon(
|
||||
value = "",
|
||||
label = TextReference.Res(R.string.send_extras_hint_memo),
|
||||
placeholder = TextReference.Res(R.string.send_optional_field),
|
||||
onValueChange = {},
|
||||
onPasteClick = {},
|
||||
)
|
||||
SpacerH8()
|
||||
TextFieldWithInfo(
|
||||
value = "Text",
|
||||
label = stringResource(R.string.send_extras_hint_memo),
|
||||
info = TextReference.Res(R.string.send_optional_field),
|
||||
footer = stringResource(R.string.send_max_fee),
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TextFieldPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
Column {
|
||||
TextFieldWithPaste(
|
||||
value = "",
|
||||
label = TextReference.Res(R.string.send_recipient),
|
||||
placeholder = TextReference.Res(R.string.send_enter_address_field),
|
||||
onValueChange = {},
|
||||
onPasteClick = {},
|
||||
)
|
||||
SpacerH8()
|
||||
TextFieldWithPasteAndIcon(
|
||||
value = "",
|
||||
label = TextReference.Res(R.string.send_extras_hint_memo),
|
||||
placeholder = TextReference.Res(R.string.send_optional_field),
|
||||
onValueChange = {},
|
||||
onPasteClick = {},
|
||||
)
|
||||
SpacerH8()
|
||||
TextFieldWithInfo(
|
||||
value = "Text",
|
||||
label = stringResource(R.string.send_extras_hint_memo),
|
||||
info = TextReference.Res(R.string.send_optional_field),
|
||||
footer = stringResource(R.string.send_max_fee),
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.toBigDecimalOrDefault
|
||||
import com.tangem.core.ui.components.inputrow.InputRowDefault
|
||||
import com.tangem.core.ui.components.inputrow.InputRowImage
|
||||
import com.tangem.core.ui.components.inputrow.InputRowRecipientDefault
|
||||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter.formatCryptoAmount
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun SendContent(uiState: SendUiState) {
|
||||
val amountState = uiState.amountState ?: return
|
||||
val recipientState = uiState.recipientState ?: return
|
||||
val feeState = uiState.feeState ?: return
|
||||
val sendState = uiState.sendState ?: return
|
||||
|
||||
val isSuccess = sendState.isSuccess.collectAsStateWithLifecycle()
|
||||
val timestamp = sendState.transactionDate.collectAsStateWithLifecycle()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
AnimatedVisibility(visible = isSuccess.value) {
|
||||
TransactionDoneTitle(
|
||||
titleRes = R.string.sent_transaction_sent_title,
|
||||
date = timestamp.value,
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(visible = !isSuccess.value) {
|
||||
FromWallet(
|
||||
walletName = amountState.walletName,
|
||||
walletBalance = amountState.walletBalance,
|
||||
)
|
||||
}
|
||||
AmountBlock(
|
||||
amountState = amountState,
|
||||
isSuccess = isSuccess,
|
||||
onClick = uiState.clickIntents::showAmount,
|
||||
)
|
||||
RecipientBlock(
|
||||
recipientState = recipientState,
|
||||
isSuccess = isSuccess,
|
||||
onClick = uiState.clickIntents::showRecipient,
|
||||
)
|
||||
FeeBlock(
|
||||
feeState = feeState,
|
||||
isSuccess = isSuccess,
|
||||
onClick = uiState.clickIntents::showFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FromWallet(walletName: String, walletBalance: String) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.button.disabled)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append(stringResource(R.string.send_from_wallet_android))
|
||||
append(" ")
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(walletName)
|
||||
}
|
||||
},
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Text(
|
||||
text = walletBalance,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountBlock(amountState: SendStates.AmountState, isSuccess: State<Boolean>, onClick: () -> Unit) {
|
||||
val amount = amountState.amountTextField.collectAsStateWithLifecycle()
|
||||
|
||||
val cryptoAmount = formatCryptoAmount(
|
||||
cryptoCurrency = amountState.cryptoCurrencyStatus.currency,
|
||||
cryptoAmount = amount.value.value.toBigDecimalOrDefault(),
|
||||
)
|
||||
val fiatAmount = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = amount.value.fiatValue.toBigDecimalOrDefault(),
|
||||
fiatCurrencyCode = amountState.appCurrency.code,
|
||||
fiatCurrencySymbol = amountState.appCurrency.symbol,
|
||||
)
|
||||
InputRowImage(
|
||||
title = TextReference.Res(R.string.send_amount_label),
|
||||
subtitle = TextReference.Str(cryptoAmount),
|
||||
caption = TextReference.Str(fiatAmount),
|
||||
tokenIconState = amountState.tokenIconState,
|
||||
showNetworkIcon = true,
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable(enabled = !isSuccess.value) { onClick() },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RecipientBlock(recipientState: SendStates.RecipientState, isSuccess: State<Boolean>, onClick: () -> Unit) {
|
||||
val address = recipientState.addressTextField.collectAsStateWithLifecycle()
|
||||
val memo = recipientState.memoTextField?.collectAsStateWithLifecycle()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable(enabled = !isSuccess.value) { onClick() },
|
||||
) {
|
||||
val showMemo = memo != null && memo.value.value.isNotBlank()
|
||||
InputRowRecipientDefault(
|
||||
title = TextReference.Res(R.string.send_recipient),
|
||||
value = address.value.value,
|
||||
showDivider = showMemo,
|
||||
)
|
||||
if (showMemo) {
|
||||
InputRowDefault(
|
||||
title = TextReference.Res(R.string.send_extras_hint_memo),
|
||||
text = TextReference.Str(memo?.value?.value.orEmpty()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeBlock(feeState: SendStates.FeeState, isSuccess: State<Boolean>, onClick: () -> Unit) {
|
||||
val feeSelector =
|
||||
feeState.feeSelectorState.collectAsStateWithLifecycle().value as? FeeSelectorState.Content ?: return
|
||||
val customValue = feeSelector.customValues.collectAsStateWithLifecycle().value.getOrNull(0)
|
||||
|
||||
val feeValue = formatCryptoAmount(
|
||||
cryptoCurrency = feeState.cryptoCurrencyStatus.currency,
|
||||
cryptoAmount = when (val selectedFee = feeSelector.fees) {
|
||||
is TransactionFee.Single -> selectedFee.normal.amount.value
|
||||
is TransactionFee.Choosable -> when (feeSelector.selectedFee) {
|
||||
FeeType.SLOW -> selectedFee.minimum.amount.value
|
||||
FeeType.MARKET -> selectedFee.normal.amount.value
|
||||
FeeType.FAST -> selectedFee.priority.amount.value
|
||||
FeeType.CUSTOM -> customValue?.value.toBigDecimalOrDefault()
|
||||
}
|
||||
},
|
||||
)
|
||||
InputRowDefault(
|
||||
title = TextReference.Res(R.string.send_network_fee_title),
|
||||
text = TextReference.Str(feeValue),
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable(enabled = !isSuccess.value) { onClick() },
|
||||
)
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import java.math.BigInteger
|
|||
|
||||
internal fun validateMemo(memo: String, cryptoCurrency: CryptoCurrency?): Boolean {
|
||||
if (cryptoCurrency == null) return false
|
||||
if (memo.isEmpty()) return true
|
||||
return when (cryptoCurrency.network.id.value) {
|
||||
Blockchain.XRP.id -> {
|
||||
val tag = memo.toLongOrNull()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
|||
|
||||
interface SendClickIntents {
|
||||
|
||||
fun popBackStack()
|
||||
|
||||
fun onBackClick()
|
||||
|
||||
fun onNextClick()
|
||||
|
|
@ -33,4 +35,16 @@ interface SendClickIntents {
|
|||
|
||||
fun onSubtractSelect(value: Boolean)
|
||||
// endregion
|
||||
|
||||
// region Send
|
||||
fun onSendClick()
|
||||
|
||||
fun showAmount()
|
||||
|
||||
fun showRecipient()
|
||||
|
||||
fun showFee()
|
||||
|
||||
fun onExploreClick(txUrl: String)
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import arrow.core.getOrElse
|
|||
import com.tangem.blockchain.blockchains.xrp.XrpAddressService
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.address.Address
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
|
|
@ -18,8 +19,11 @@ import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
|||
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.utils.convertToAmount
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -28,6 +32,7 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.send.impl.navigation.InnerSendRouter
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.state.SendStateFactory
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
|
|
@ -48,7 +53,7 @@ import java.math.BigDecimal
|
|||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "TooManyFunctions", "LargeClass")
|
||||
@HiltViewModel
|
||||
internal class SendViewModel @Inject constructor(
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -60,6 +65,8 @@ internal class SendViewModel @Inject constructor(
|
|||
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val getFeeUseCase: GetFeeUseCase,
|
||||
private val sendTransactionUseCase: SendTransactionUseCase,
|
||||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, SendClickIntents {
|
||||
|
|
@ -73,7 +80,8 @@ internal class SendViewModel @Inject constructor(
|
|||
|
||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||
|
||||
private var innerRouter: StateRouter by Delegates.notNull()
|
||||
private var inneRrouter: InnerSendRouter by Delegates.notNull()
|
||||
private var stateRouter: StateRouter by Delegates.notNull()
|
||||
|
||||
private val stateFactory = SendStateFactory(
|
||||
clickIntents = this,
|
||||
|
|
@ -102,9 +110,10 @@ internal class SendViewModel @Inject constructor(
|
|||
getFee()
|
||||
}
|
||||
|
||||
fun setRouter(router: StateRouter) {
|
||||
innerRouter = router
|
||||
uiState = uiState.copy(currentState = router.currentState)
|
||||
fun setRouter(router: InnerSendRouter, stateRouter: StateRouter) {
|
||||
inneRrouter = router
|
||||
this.stateRouter = stateRouter
|
||||
uiState = uiState.copy(currentState = stateRouter.currentState)
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates(owner: LifecycleOwner) {
|
||||
|
|
@ -271,9 +280,10 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
// region screen state navigation
|
||||
override fun onBackClick() = innerRouter.onBackClick()
|
||||
override fun onNextClick() = innerRouter.onNextClick()
|
||||
override fun onPrevClick() = innerRouter.onPrevClick()
|
||||
override fun popBackStack() = stateRouter.popBackStack()
|
||||
override fun onBackClick() = stateRouter.onBackClick()
|
||||
override fun onNextClick() = stateRouter.onNextClick()
|
||||
override fun onPrevClick() = stateRouter.onPrevClick()
|
||||
|
||||
override fun onQrCodeScanClick() {
|
||||
// TODO Add QR code scanning
|
||||
|
|
@ -314,7 +324,7 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun checkIfXrpAddressValue(value: String): Boolean {
|
||||
if (cryptoCurrency.network.id.value == Blockchain.XRP.id && value.first() == XRP_X_ADDRESS) {
|
||||
if (cryptoCurrency.network.id.value == Blockchain.XRP.id && value.firstOrNull() == XRP_X_ADDRESS) {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val result = XrpAddressService.decodeXAddress(value)
|
||||
onRecipientAddressValueChange(result?.address.orEmpty())
|
||||
|
|
@ -382,8 +392,108 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
//endregion
|
||||
|
||||
// region send state clicks
|
||||
override fun onSendClick() {
|
||||
val sendState = uiState.sendState ?: return
|
||||
|
||||
if (sendState.isSuccess.value) popBackStack()
|
||||
sendState.isSending.update { true }
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
verifyAndSendTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun verifyAndSendTransaction() {
|
||||
val sendState = uiState.sendState ?: return
|
||||
val amount = uiState.amountState?.amountTextField?.value ?: return
|
||||
val recipient = uiState.recipientState?.addressTextField?.value ?: return
|
||||
val feeState = uiState.feeState?.feeSelectorState?.value as? FeeSelectorState.Content ?: return
|
||||
val memo = uiState.recipientState?.memoTextField?.value
|
||||
val fee = getFee(feeState) ?: return
|
||||
|
||||
val amountToSend = amount.value.toBigDecimal().convertToAmount(cryptoCurrency)
|
||||
|
||||
// todo add notifications [[REDACTED_JIRA]]
|
||||
// val transactionErrors = walletManagersFacade.validateTransaction(
|
||||
// amount = amountToSend,
|
||||
// fee = fee.amount,
|
||||
// userWalletId = userWalletId,
|
||||
// network = cryptoCurrency.network,
|
||||
// )
|
||||
|
||||
val txData = walletManagersFacade.createTransaction(
|
||||
amount = amountToSend,
|
||||
fee = fee,
|
||||
memo = memo?.value,
|
||||
destination = recipient.value,
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
) ?: return
|
||||
|
||||
sendTransactionUseCase(
|
||||
txData = txData,
|
||||
userWallet = userWallet,
|
||||
network = cryptoCurrency.network,
|
||||
).fold(
|
||||
ifLeft = {
|
||||
sendState.isSending.update { false }
|
||||
// todo add notifications [[REDACTED_JIRA]]
|
||||
},
|
||||
ifRight = {
|
||||
sendState.transactionDate.update {
|
||||
txData.date?.timeInMillis ?: System.currentTimeMillis()
|
||||
}
|
||||
sendState.isSuccess.update { true }
|
||||
sendState.txUrl.update {
|
||||
getTxUrl(txData.hash.orEmpty())
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun getFee(feeState: FeeSelectorState.Content): Fee? {
|
||||
return when (val selectedFee = feeState.fees) {
|
||||
is TransactionFee.Choosable -> {
|
||||
when (feeState.selectedFee) {
|
||||
FeeType.SLOW -> selectedFee.minimum
|
||||
FeeType.MARKET -> selectedFee.normal
|
||||
FeeType.FAST -> selectedFee.priority
|
||||
FeeType.CUSTOM -> {
|
||||
val feeAmount = feeState.customValues.value.firstOrNull()?.value
|
||||
?.let { BigDecimal(it) } ?: return null
|
||||
Fee.Common(feeAmount.convertToAmount(cryptoCurrency))
|
||||
}
|
||||
}
|
||||
}
|
||||
is TransactionFee.Single -> selectedFee.normal
|
||||
}
|
||||
}
|
||||
|
||||
override fun showAmount() = stateRouter.showAmount(isFromSend = true)
|
||||
|
||||
override fun showRecipient() = stateRouter.showRecipient(isFromSend = true)
|
||||
|
||||
override fun showFee() = stateRouter.showFee(isFromSend = true)
|
||||
|
||||
override fun onExploreClick(txUrl: String) = inneRrouter.openUrl(txUrl)
|
||||
|
||||
private fun getTxUrl(hash: String): String {
|
||||
val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value)
|
||||
// TODO: Fix ton tx urls [REDACTED_TASK_KEY]
|
||||
return if (blockchain == Blockchain.TON || blockchain == Blockchain.TONTestnet) {
|
||||
EMPTY
|
||||
} else {
|
||||
getExplorerTransactionUrlUseCase(
|
||||
txHash = hash,
|
||||
networkId = cryptoCurrency.network.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
private const val XRP_X_ADDRESS = 'X'
|
||||
private const val DEFAULT_VALUE = "0.00"
|
||||
private const val EMPTY = ""
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue