diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
index 5b68512d77..8dfb19bd16 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
@@ -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,
+
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt
index 74815a675f..b584136754 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt
@@ -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,
)
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt
index 847c27307f..91dd9a2506 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt
@@ -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
*/
@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()
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt
index c4d41ef91b..8e0081a633 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt
@@ -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,
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt
index b6b858d2cd..d0d3315f20 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt
@@ -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 Figma component
+ */
+@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
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt
index 73509da21f..6151499106 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt
@@ -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(
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountVisualTransformation.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt
similarity index 93%
rename from core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountVisualTransformation.kt
rename to core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt
index 89e4fa09ce..b3f27e218e 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountVisualTransformation.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt
@@ -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
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt
new file mode 100644
index 0000000000..d576eafe66
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt
@@ -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
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt b/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt
index 362f4d95e9..4a014cd824 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt
@@ -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
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
index d8e209f0ad..c64c860e28 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
@@ -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,
diff --git a/core/ui/src/main/res/drawable/ic_empty_in_process_64.xml b/core/ui/src/main/res/drawable/ic_empty_in_process_64.xml
new file mode 100644
index 0000000000..e75b15979e
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_empty_in_process_64.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_web_24.xml b/core/ui/src/main/res/drawable/ic_web_24.xml
new file mode 100644
index 0000000000..69e2fc27d9
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_web_24.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/data/card/build.gradle.kts b/data/card/build.gradle.kts
index 6015c1ed3a..872f5ce751 100644
--- a/data/card/build.gradle.kts
+++ b/data/card/build.gradle.kts
@@ -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)
diff --git a/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt b/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt
index a093b24ba0..97c5e7dc35 100644
--- a/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt
+++ b/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt
@@ -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
+ }
}
\ No newline at end of file
diff --git a/domain/card/build.gradle.kts b/domain/card/build.gradle.kts
index 71512156cd..342a410f5a 100644
--- a/domain/card/build.gradle.kts
+++ b/domain/card/build.gradle.kts
@@ -19,4 +19,8 @@ dependencies {
implementation(deps.tangem.card.core)
+ implementation(deps.tangem.blockchain) {
+ exclude(module = "joda-time")
+ }
+
}
\ No newline at end of file
diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt
index 1190a2952d..47d13a5a8c 100644
--- a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt
+++ b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt
@@ -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?)
}
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
index 15010e8b52..df99d53034 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
@@ -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? {
+ 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) {
if (tokens.isEmpty()) return
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
index 8e830e0bdd..f2b89b51a6 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
@@ -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?
+
+ /**
+ * 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?
+
+ /**
+ * 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
}
\ No newline at end of file
diff --git a/domain/tokens/models/build.gradle.kts b/domain/tokens/models/build.gradle.kts
index e9075cefa0..a354fa4700 100644
--- a/domain/tokens/models/build.gradle.kts
+++ b/domain/tokens/models/build.gradle.kts
@@ -12,4 +12,7 @@ android {
dependencies {
implementation(projects.domain.txhistory.models)
implementation(projects.core.analytics.models)
+ implementation(deps.tangem.blockchain) {
+ exclude(module = "joda-time")
+ }
}
\ No newline at end of file
diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/utils/BigDecimalUtils.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/utils/BigDecimalUtils.kt
new file mode 100644
index 0000000000..fe93d1d8ec
--- /dev/null
+++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/utils/BigDecimalUtils.kt
@@ -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,
+ ),
+ )
+ },
+)
\ No newline at end of file
diff --git a/domain/transaction/build.gradle.kts b/domain/transaction/build.gradle.kts
index 8da795757a..571b6c0f50 100644
--- a/domain/transaction/build.gradle.kts
+++ b/domain/transaction/build.gradle.kts
@@ -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)
}
\ No newline at end of file
diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt
new file mode 100644
index 0000000000..26fdceff5a
--- /dev/null
+++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt
new file mode 100644
index 0000000000..0a289bfcdc
--- /dev/null
+++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt
@@ -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 {
+ 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() },
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/ChooseWalletState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/ChooseWalletState.kt
new file mode 100644
index 0000000000..44a0529872
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/ChooseWalletState.kt
@@ -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,
+ 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,
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt
new file mode 100644
index 0000000000..a759836afc
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt
@@ -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,
+ val isMainNetwork: Boolean,
+ val isAdded: MutableState,
+ 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
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/WalletState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/WalletState.kt
new file mode 100644
index 0000000000..6af48736a4
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/WalletState.kt
@@ -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,
+)
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/previewdata/ChooseWalletStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/previewdata/ChooseWalletStatePreviewData.kt
new file mode 100644
index 0000000000..f231654b96
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/previewdata/ChooseWalletStatePreviewData.kt
@@ -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 = {},
+ )
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt
new file mode 100644
index 0000000000..d0dab333d8
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt
@@ -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,
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt
new file mode 100644
index 0000000000..18de0a171a
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt
@@ -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(
+ 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,
+ ),
+ ),
+)
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt
new file mode 100644
index 0000000000..c136cb1e28
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt
@@ -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 = { })
+ }
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ChooseNetworkState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ChooseNetworkState.kt
new file mode 100644
index 0000000000..df2a7f015f
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ChooseNetworkState.kt
@@ -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,
+ val nonNativeNetworks: ImmutableList,
+ val onNonNativeNetworkHintClick: () -> Unit,
+ val onCloseChooseNetworkScreen: () -> Unit,
+)
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/DerivationNotificationState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/DerivationNotificationState.kt
index 8856ab58c5..0cfdfa16d1 100644
--- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/DerivationNotificationState.kt
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/DerivationNotificationState.kt
@@ -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),
),
),
)
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/TokenItemState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/TokenItemState.kt
index ae386cc7c1..7262bbe337 100644
--- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/TokenItemState.kt
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/TokenItemState.kt
@@ -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,
+ val chooseNetworkState: ChooseNetworkState,
+ val onButtonClick: (Loaded) -> Unit,
) : TokenItemState()
}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt
new file mode 100644
index 0000000000..12be7db588
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt
@@ -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,
+ ),
+)
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/DerivationNotificationStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/DerivationNotificationStatePreviewData.kt
index 207ea75cb8..b5d88b814a 100644
--- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/DerivationNotificationStatePreviewData.kt
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/DerivationNotificationStatePreviewData.kt
@@ -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 = {},
)
}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/TokenItemStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/TokenItemStatePreviewData.kt
index bdfedacd3e..01ed9bdda9 100644
--- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/TokenItemStatePreviewData.kt
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/TokenItemStatePreviewData.kt
@@ -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
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt
new file mode 100644
index 0000000000..ebc600073f
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt
@@ -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,
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt
index 139e4f9652..12a403e3e4 100644
--- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt
+++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt
@@ -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) },
)
}
}
diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts
index bb59a8e295..1af2bafaee 100644
--- a/features/send/impl/build.gradle.kts
+++ b/features/send/impl/build.gradle.kts
@@ -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)
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendRouterModule.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendRouterModule.kt
index 8bf19609d8..7f38cacd35 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendRouterModule.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/di/SendRouterModule.kt
@@ -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)
}
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt
index 4db1a5a674..c72ca92ae7 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/DefaultSendRouter.kt
@@ -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))
+ }
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt
new file mode 100644
index 0000000000..b7cd7f934a
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/navigation/InnerSendRouter.kt
@@ -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)
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt
index 2790caab93..cd47d6c8fe 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt
@@ -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()
+ 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),
),
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendNotification.kt
new file mode 100644
index 0000000000..720270d86d
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendNotification.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
index 38583d0676..bae35722ae 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
@@ -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,
)
}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
index 207e63064d..1d7474d6b4 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
@@ -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> = MutableStateFlow(PagingData.empty()),
val currentState: MutableStateFlow,
)
@@ -65,11 +66,14 @@ internal sealed class SendStates {
val receivedAmount: MutableStateFlow = MutableStateFlow(""),
) : SendStates()
- // todo [REDACTED_JIRA]
/** Send state */
data class SendState(
- val isSuccess: Boolean,
- )
+ override val type: SendUiStateType = SendUiStateType.Send,
+ val isSending: MutableStateFlow = MutableStateFlow(false),
+ val isSuccess: MutableStateFlow = MutableStateFlow(false),
+ val transactionDate: MutableStateFlow = MutableStateFlow(0L),
+ val txUrl: MutableStateFlow = MutableStateFlow(""),
+ ) : SendStates()
}
enum class SendUiStateType {
@@ -77,5 +81,4 @@ enum class SendUiStateType {
Recipient,
Fee,
Send,
- Done,
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt
index e50ae05ae8..e92791bba9 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt
@@ -9,28 +9,61 @@ internal class StateRouter(
private val fragmentManager: WeakReference,
) {
var currentState: MutableStateFlow = 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 }
+ }
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt
index d1890038cb..a1c0bd5603 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt
@@ -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),
),
)
}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt
index 30ca727485..e7c0df4249 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt
@@ -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,
+ isSuccess: Boolean,
+): Pair 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
+ }
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt
index c8f289df54..1c73c25a04 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt
@@ -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)
}
}
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt
index 2fedacc5c1..f6471c020d 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt
@@ -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
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt
index a96d8c5f1b..98b24927f0 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt
@@ -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,
+ ),
+ )
+ }
}
}
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt
index 30563155b4..24319091e1 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt
@@ -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) {
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt
new file mode 100644
index 0000000000..8b3c83bfb0
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt
@@ -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),
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFields.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFields.kt
deleted file mode 100644
index f2a792f03e..0000000000
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFields.kt
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
new file mode 100644
index 0000000000..037913ef69
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
@@ -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, 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, 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, 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() },
+ )
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/MemoVerification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/MemoVerification.kt
index a27ce13f23..ae24d705fd 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/MemoVerification.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/MemoVerification.kt
@@ -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()
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
index 35df0a6614..d29ab95bee 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
@@ -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
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
index 02f2caefc6..4fdc321d9f 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
@@ -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 = 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 = ""
}
}
\ No newline at end of file