Updated on 2026-08-14
This commit is contained in:
parent
e2af488b32
commit
fdbd09cccb
20 changed files with 852 additions and 77 deletions
|
|
@ -32,6 +32,7 @@ dependencies {
|
|||
implementation(projects.features.txhistory.api)
|
||||
implementation(projects.features.tokendetails.api)
|
||||
implementation(projects.features.promoBanners.api)
|
||||
implementation(projects.features.virtualAccounts.details.api) // TWI_1638_VA_MVP0_ENABLED
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.balanceHiding)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
|||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.account.VirtualAccountOnramp
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.TangemPayTopUpData
|
||||
|
|
@ -39,11 +40,13 @@ internal class TangemPayAddFundsComponent(
|
|||
val fiatBalance: BigDecimal,
|
||||
val depositAddress: String,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val virtualAccountOnramp: VirtualAccountOnramp?,
|
||||
)
|
||||
}
|
||||
|
||||
internal interface AddFundsListener {
|
||||
fun onClickReceive(data: TangemPayTopUpData)
|
||||
fun onClickSwap(data: TangemPayTopUpData)
|
||||
fun onClickBankTransfer()
|
||||
fun onDismissAddFunds()
|
||||
}
|
||||
|
|
@ -97,6 +97,14 @@ internal class TangemPayCardPageScreenComponent(
|
|||
fiatBalance = navigation.fiatBalance,
|
||||
depositAddress = navigation.depositAddress,
|
||||
cryptoCurrency = navigation.cryptoCurrency,
|
||||
virtualAccountOnramp = navigation.virtualAccountOnramp,
|
||||
),
|
||||
)
|
||||
is TangemPayCardNavigation.VirtualAccountDeposit -> TangemPayVirtualAccountDepositComponent(
|
||||
appComponentContext = context,
|
||||
params = TangemPayVirtualAccountDepositComponent.Params(
|
||||
virtualAccountOnramp = navigation.virtualAccountOnramp,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
is TangemPayCardNavigation.Receive -> tokenReceiveComponentFactory.create(
|
||||
|
|
|
|||
|
|
@ -143,6 +143,14 @@ internal class TangemPayDetailsComponent(
|
|||
depositAddress = navigation.depositAddress,
|
||||
cryptoCurrency = navigation.cryptoCurrency,
|
||||
listener = model,
|
||||
virtualAccountOnramp = navigation.virtualAccountOnramp,
|
||||
),
|
||||
)
|
||||
is TangemPayDetailsNavigation.VirtualAccountDeposit -> TangemPayVirtualAccountDepositComponent(
|
||||
appComponentContext = context,
|
||||
params = TangemPayVirtualAccountDepositComponent.Params(
|
||||
virtualAccountOnramp = navigation.virtualAccountOnramp,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
is TangemPayDetailsNavigation.IssueAdditionalCard -> TangemPayIssueAdditionalCardComponent(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.features.tangempay.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.account.VirtualAccountOnramp
|
||||
import com.tangem.features.tangempay.model.TangemPayVirtualAccountDepositModel
|
||||
import com.tangem.features.tangempay.ui.TangemPayVirtualAccountDepositBottomSheet
|
||||
|
||||
/**
|
||||
* Bank-transfer deposit bottom sheet (VA MVP0, TWI-1638). Opened from the add-funds "Bank transfer" option.
|
||||
* Renders the on-ramp intro; the [VirtualAccountOnramp.Eligible] state additionally shows a T&C consent footer.
|
||||
*/
|
||||
internal class TangemPayVirtualAccountDepositComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: Params,
|
||||
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: TangemPayVirtualAccountDepositModel = getOrCreateModel(params = params)
|
||||
|
||||
override fun dismiss() {
|
||||
model.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
TangemPayVirtualAccountDepositBottomSheet(state = model.uiState)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val virtualAccountOnramp: VirtualAccountOnramp,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -46,6 +46,11 @@ internal interface TangemPayModelModule {
|
|||
@ClassKey(TangemPayAddFundsModel::class)
|
||||
fun bindTangemPayAddFundsModel(model: TangemPayAddFundsModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(TangemPayVirtualAccountDepositModel::class)
|
||||
fun bindTangemPayVirtualAccountDepositModel(model: TangemPayVirtualAccountDepositModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(TangemPayViewPinModel::class)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.account.VirtualAccountOnramp
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -30,6 +31,12 @@ internal sealed class TangemPayCardNavigation {
|
|||
val fiatBalance: SerializedBigDecimal,
|
||||
val depositAddress: String,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val virtualAccountOnramp: VirtualAccountOnramp?,
|
||||
) : TangemPayCardNavigation()
|
||||
|
||||
@Serializable
|
||||
data class VirtualAccountDeposit(
|
||||
val virtualAccountOnramp: VirtualAccountOnramp,
|
||||
) : TangemPayCardNavigation()
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.account.VirtualAccountOnramp
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.models.serialization.SerializedCurrency
|
||||
|
|
@ -21,6 +22,12 @@ internal sealed class TangemPayDetailsNavigation {
|
|||
val fiatBalance: SerializedBigDecimal,
|
||||
val depositAddress: String,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val virtualAccountOnramp: VirtualAccountOnramp?,
|
||||
) : TangemPayDetailsNavigation()
|
||||
|
||||
@Serializable
|
||||
data class VirtualAccountDeposit(
|
||||
val virtualAccountOnramp: VirtualAccountOnramp,
|
||||
) : TangemPayDetailsNavigation()
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* UI state of the bank-transfer deposit bottom sheet (VA MVP0, TWI-1638).
|
||||
*
|
||||
* @property shouldShowTermsAndConditions `true` for the `Eligible` state — shows the provider T&C consent footer.
|
||||
*/
|
||||
@Immutable
|
||||
internal data class TangemPayVirtualAccountDepositUM(
|
||||
val fees: ImmutableList<FeeRow>,
|
||||
val shouldShowTermsAndConditions: Boolean,
|
||||
val onShowDetailsClick: () -> Unit,
|
||||
val onDismiss: () -> Unit,
|
||||
val onTermsClick: () -> Unit,
|
||||
val onPrivacyClick: () -> Unit,
|
||||
) {
|
||||
|
||||
@Immutable
|
||||
data class FeeRow(
|
||||
val title: TextReference,
|
||||
val value: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.features.tangempay.TangemPayFeatureToggles
|
|||
import com.tangem.features.tangempay.components.TangemPayAddFundsComponent
|
||||
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
|
||||
import com.tangem.features.tangempay.model.transformers.TangemPayAddFundsUMConverter
|
||||
import com.tangem.features.virtualaccount.VirtualAccountFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ internal class TangemPayAddFundsModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val virtualAccountToggles: VirtualAccountFeatureToggles,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<TangemPayAddFundsComponent.Params>()
|
||||
|
|
@ -43,6 +45,7 @@ internal class TangemPayAddFundsModel @Inject constructor(
|
|||
return TangemPayAddFundsUMConverter(
|
||||
listener = params.listener,
|
||||
isRedesignEnabled = tangemPayFeatureToggles.isRedesignEnabled,
|
||||
shouldShowBankTransfer = virtualAccountToggles.isVaMvp0Enabled && params.virtualAccountOnramp != null,
|
||||
).convert(data)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
cryptoBalance = balance.cryptoBalance.balance,
|
||||
depositAddress = balance.cryptoBalance.depositAddress,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
virtualAccountOnramp = currentStatus.value.ifLoadedOrNull { it.virtualAccount },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -484,6 +485,12 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun onClickBankTransfer() {
|
||||
val onramp = currentStatus.value.ifLoadedOrNull { it.virtualAccount } ?: return
|
||||
bottomSheetNavigation.dismiss()
|
||||
bottomSheetNavigation.activate(TangemPayCardNavigation.VirtualAccountDeposit(onramp))
|
||||
}
|
||||
|
||||
override fun onDismissAddFunds() {
|
||||
bottomSheetNavigation.dismiss()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
cryptoBalance = balance.availableForWithdrawal,
|
||||
depositAddress = balance.cryptoBalance.depositAddress,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
virtualAccountOnramp = currentStatus.value.ifLoadedOrNull { it.virtualAccount },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -344,6 +345,12 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun onClickBankTransfer() {
|
||||
val onramp = currentStatus.value.ifLoadedOrNull { it.virtualAccount } ?: return
|
||||
bottomSheetNavigation.dismiss()
|
||||
bottomSheetNavigation.activate(TangemPayDetailsNavigation.VirtualAccountDeposit(onramp))
|
||||
}
|
||||
|
||||
override fun onClickReceive(data: TangemPayTopUpData) {
|
||||
analytics.send(TangemPayAnalyticsEvents.ReceiveFundsClicked())
|
||||
bottomSheetNavigation.dismiss()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.features.tangempay.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.account.VirtualAccountOnramp
|
||||
import com.tangem.features.tangempay.components.TangemPayVirtualAccountDepositComponent
|
||||
import com.tangem.features.tangempay.entity.TangemPayVirtualAccountDepositUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class TangemPayVirtualAccountDepositModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val urlOpener: UrlOpener,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<TangemPayVirtualAccountDepositComponent.Params>()
|
||||
|
||||
val uiState: TangemPayVirtualAccountDepositUM = TangemPayVirtualAccountDepositUM(
|
||||
fees = persistentListOf(
|
||||
TangemPayVirtualAccountDepositUM.FeeRow(title = stringReference("ACH"), value = "$1"),
|
||||
TangemPayVirtualAccountDepositUM.FeeRow(title = stringReference("FedWire"), value = "$11"),
|
||||
),
|
||||
shouldShowTermsAndConditions = params.virtualAccountOnramp is VirtualAccountOnramp.Eligible,
|
||||
onShowDetailsClick = ::onShowDetailsClick,
|
||||
onDismiss = ::onDismiss,
|
||||
onTermsClick = { urlOpener.openUrl(TERMS_OF_USE_URL) },
|
||||
onPrivacyClick = { urlOpener.openUrl(PRIVACY_POLICY_URL) },
|
||||
)
|
||||
|
||||
fun onDismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
||||
private fun onShowDetailsClick() {
|
||||
// TODO([REDACTED_TASK_KEY]): VA MVP0 — open the bank-transfer requisites screen (separate PR).
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TERMS_OF_USE_URL = "https://tangem.com/docs/en/virtual-account-terms.pdf"
|
||||
const val PRIVACY_POLICY_URL = "https://tangem.com/docs/en/pay-privacy-policy.pdf"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +1,89 @@
|
|||
package com.tangem.features.tangempay.model.transformers
|
||||
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_card_20
|
||||
import com.tangem.core.ui.res.generated.icons.ic_logo_tangem_20
|
||||
import com.tangem.core.ui.res.generated.icons.ic_sign_usd_20
|
||||
import com.tangem.domain.pay.model.TangemPayTopUpData
|
||||
import com.tangem.features.tangempay.components.AddFundsListener
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.TangemPayAddFundsItemUM
|
||||
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
|
||||
import com.tangem.features.tangempay.utils.TangemPayMessagesFactory
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import com.tangem.utils.extensions.addIf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class TangemPayAddFundsUMConverter(
|
||||
val listener: AddFundsListener,
|
||||
val isRedesignEnabled: Boolean,
|
||||
) : Converter<TangemPayTopUpData?, TangemPayAddFundsUM> {
|
||||
val shouldShowBankTransfer: Boolean,
|
||||
) : Converter<TangemPayTopUpData, TangemPayAddFundsUM> {
|
||||
|
||||
override fun convert(value: TangemPayTopUpData?): TangemPayAddFundsUM {
|
||||
return if (value == null) {
|
||||
TangemPayAddFundsUM(
|
||||
items = persistentListOf(),
|
||||
dismiss = listener::onDismissAddFunds,
|
||||
errorMessage = TangemPayMessagesFactory.createErrorMessage(
|
||||
errorType = TangemPayDetailsErrorType.Receive,
|
||||
).messageBottomSheetUM,
|
||||
)
|
||||
} else {
|
||||
TangemPayAddFundsUM(
|
||||
items = persistentListOf(
|
||||
TangemPayAddFundsItemUM(
|
||||
icon = if (isRedesignEnabled) {
|
||||
TangemIconUM.Icon(
|
||||
imageVector = Icons.ic_logo_tangem_20,
|
||||
tintReference = {
|
||||
TangemTheme.colors3.icon.brand
|
||||
},
|
||||
)
|
||||
} else {
|
||||
TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_exchange_vertical_24,
|
||||
tintReference = {
|
||||
TangemTheme.colors.icon.accent
|
||||
},
|
||||
)
|
||||
},
|
||||
title = TextReference.Res(R.string.tangempay_topup_swap_title),
|
||||
description = TextReference.Res(R.string.tangempay_topup_swap_body),
|
||||
onClick = { listener.onClickSwap(value) },
|
||||
),
|
||||
TangemPayAddFundsItemUM(
|
||||
icon = if (isRedesignEnabled) {
|
||||
TangemIconUM.Icon(
|
||||
imageVector = Icons.ic_card_20,
|
||||
tintReference = {
|
||||
TangemTheme.colors3.icon.brand
|
||||
},
|
||||
)
|
||||
} else {
|
||||
TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_arrow_down_24,
|
||||
tintReference = {
|
||||
TangemTheme.colors.icon.accent
|
||||
},
|
||||
)
|
||||
},
|
||||
title = TextReference.Res(R.string.tangempay_topup_receive_title),
|
||||
description = TextReference.Res(R.string.tangempay_topup_receive_body),
|
||||
onClick = { listener.onClickReceive(value) },
|
||||
),
|
||||
),
|
||||
dismiss = listener::onDismissAddFunds,
|
||||
errorMessage = null,
|
||||
)
|
||||
}
|
||||
@Suppress("UnnecessaryLet")
|
||||
override fun convert(value: TangemPayTopUpData): TangemPayAddFundsUM {
|
||||
return TangemPayAddFundsUM(
|
||||
items = buildList {
|
||||
TangemPayAddFundsItemUM(
|
||||
icon = if (isRedesignEnabled) {
|
||||
TangemIconUM.Icon(
|
||||
imageVector = Icons.ic_logo_tangem_20,
|
||||
tintReference = {
|
||||
TangemTheme.colors3.icon.brand
|
||||
},
|
||||
)
|
||||
} else {
|
||||
TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_exchange_vertical_24,
|
||||
tintReference = {
|
||||
TangemTheme.colors.icon.accent
|
||||
},
|
||||
)
|
||||
},
|
||||
title = resourceReference(R.string.tangempay_topup_swap_title),
|
||||
description = resourceReference(R.string.tangempay_topup_swap_body),
|
||||
onClick = { listener.onClickSwap(value) },
|
||||
).let(::add)
|
||||
TangemPayAddFundsItemUM(
|
||||
icon = if (isRedesignEnabled) {
|
||||
TangemIconUM.Icon(
|
||||
imageVector = Icons.ic_card_20,
|
||||
tintReference = {
|
||||
TangemTheme.colors3.icon.brand
|
||||
},
|
||||
)
|
||||
} else {
|
||||
TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_arrow_down_24,
|
||||
tintReference = {
|
||||
TangemTheme.colors.icon.accent
|
||||
},
|
||||
)
|
||||
},
|
||||
title = resourceReference(R.string.tangempay_topup_receive_title),
|
||||
description = resourceReference(R.string.tangempay_topup_receive_body),
|
||||
onClick = { listener.onClickReceive(value) },
|
||||
).let(::add)
|
||||
addIf(
|
||||
condition = shouldShowBankTransfer,
|
||||
create = {
|
||||
TangemPayAddFundsItemUM(
|
||||
icon = TangemIconUM.Icon(
|
||||
imageVector = Icons.ic_sign_usd_20,
|
||||
tintReference = { TangemTheme.colors3.icon.brand },
|
||||
),
|
||||
title = stringReference("Bank transfer"),
|
||||
description = stringReference("Receive fiat USD via ACH/FedWire"),
|
||||
onClick = listener::onClickBankTransfer,
|
||||
)
|
||||
},
|
||||
)
|
||||
}.toPersistentList(),
|
||||
dismiss = listener::onDismissAddFunds,
|
||||
errorMessage = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withLink
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
||||
import com.tangem.core.ui.ds2.button.Close
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.row.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_info_24
|
||||
import com.tangem.core.ui.res.generated.icons.ic_sign_usd_32
|
||||
import com.tangem.features.tangempay.entity.TangemPayVirtualAccountDepositUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayVirtualAccountDepositBottomSheet(state: TangemPayVirtualAccountDepositUM) {
|
||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = state.onDismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
type = TangemBottomSheetType.Modal,
|
||||
containerColor = TangemTheme.colors3.bg.secondary,
|
||||
title = {
|
||||
TangemTopBar(
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
title = null,
|
||||
endContent = { TangemButton.Close(onClick = state.onDismiss) },
|
||||
)
|
||||
},
|
||||
content = { _ -> DepositContent(state) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DepositContent(state: TangemPayVirtualAccountDepositUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens2.x4)
|
||||
.padding(bottom = TangemTheme.dimens2.x4),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
IntroIcons(modifier = Modifier.padding(top = TangemTheme.dimens2.x4))
|
||||
TitleText(
|
||||
text = stringReference("Bank transfer might take 1-2 business days"),
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens2.x8),
|
||||
)
|
||||
SubtitleText(
|
||||
text = stringReference("Received USD will be converted to USDC by 1:1 rate"),
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens2.x2),
|
||||
)
|
||||
FeesBlock(
|
||||
fees = state.fees,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens2.x6),
|
||||
)
|
||||
InfoNotification(
|
||||
text = stringReference("Deposit via ACH or FedWire only. SWIFT transfers will be returned."),
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens2.x4),
|
||||
)
|
||||
TangemButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens2.x4),
|
||||
text = stringReference("Show details"),
|
||||
variant = TangemButton.Variant.Primary,
|
||||
size = TangemButton.Size.X12,
|
||||
onClick = state.onShowDetailsClick,
|
||||
)
|
||||
if (state.shouldShowTermsAndConditions) {
|
||||
TermsFooter(
|
||||
onTermsClick = state.onTermsClick,
|
||||
onPrivacyClick = state.onPrivacyClick,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens2.x3),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeesBlock(fees: ImmutableList<TangemPayVirtualAccountDepositUM.FeeRow>, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
modifier = Modifier.padding(
|
||||
start = TangemTheme.dimens2.x4,
|
||||
bottom = TangemTheme.dimens2.x2,
|
||||
),
|
||||
text = stringReference("Fee for onramp").resolveReference(),
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
)
|
||||
fees.forEachIndexed { index, fee ->
|
||||
TangemRow(
|
||||
contentLead = TangemRowContentLead.Equal,
|
||||
verticalAlignment = TangemRowVerticalAlignment.Center,
|
||||
divider = index != fees.lastIndex,
|
||||
titleSlot = { TangemRowText(text = fee.title, role = TangemRowTextRole.Title) },
|
||||
valueSlot = { TangemRowText(text = stringReference(fee.value), role = TangemRowTextRole.Value) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoNotification(text: TextReference, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens2.x4))
|
||||
.background(TangemTheme.colors3.bg.status.infoSubtle)
|
||||
.padding(TangemTheme.dimens2.x4),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x5),
|
||||
imageVector = Icons.ic_info_24,
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors3.icon.status.info,
|
||||
)
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography3.subheading.medium,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TermsFooter(onTermsClick: () -> Unit, onPrivacyClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
val linkStyle = SpanStyle(color = TangemTheme.colors3.text.primary)
|
||||
val text = buildAnnotatedString {
|
||||
append("By using service, you agree with provider ")
|
||||
withLink(LinkAnnotation.Clickable(tag = "terms", linkInteractionListener = { onTermsClick() })) {
|
||||
withStyle(linkStyle) { append("Terms of Use") }
|
||||
}
|
||||
append(" and ")
|
||||
withLink(LinkAnnotation.Clickable(tag = "privacy", linkInteractionListener = { onPrivacyClick() })) {
|
||||
withStyle(linkStyle) { append("Privacy Policy") }
|
||||
}
|
||||
}
|
||||
Text(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
text = text,
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IntroIcons(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(-TangemTheme.dimens2.x4),
|
||||
) {
|
||||
UsdIcon()
|
||||
UsdcIcon()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UsdIcon(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens2.x20)
|
||||
.clip(CircleShape)
|
||||
.background(TangemTheme.colors3.bg.opaque.primary)
|
||||
.border(width = 1.dp, color = TangemTheme.colors3.border.secondary, shape = CircleShape),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x8),
|
||||
imageVector = Icons.ic_sign_usd_32,
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors3.icon.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UsdcIcon(modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier.size(TangemTheme.dimens2.x20)) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(CircleShape)
|
||||
.border(width = 1.dp, color = TangemTheme.colors3.border.secondary, shape = CircleShape),
|
||||
painter = painterResource(CoreUiR.drawable.img_usdc_16),
|
||||
contentDescription = null,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.size(TangemTheme.dimens2.x6)
|
||||
.background(color = TangemTheme.colors3.bg.accent.violet, shape = CircleShape)
|
||||
.border(
|
||||
width = TangemTheme.dimens2.x0_5,
|
||||
color = TangemTheme.colors3.bg.secondary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x4),
|
||||
painter = painterResource(CoreUiR.drawable.ic_polygon_22),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors3.icon.inverse,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TitleText(text: TextReference, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography3.heading.small,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SubtitleText(text: TextReference, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography3.subheading.medium,
|
||||
color = TangemTheme.colors3.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
|
||||
private fun previewState(shouldShowTermsAndConditions: Boolean) = TangemPayVirtualAccountDepositUM(
|
||||
fees = persistentListOf(
|
||||
TangemPayVirtualAccountDepositUM.FeeRow(title = stringReference("ACH"), value = "$1"),
|
||||
TangemPayVirtualAccountDepositUM.FeeRow(title = stringReference("FedWire"), value = "$11"),
|
||||
),
|
||||
shouldShowTermsAndConditions = shouldShowTermsAndConditions,
|
||||
onShowDetailsClick = {},
|
||||
onDismiss = {},
|
||||
onTermsClick = {},
|
||||
onPrivacyClick = {},
|
||||
)
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun DepositEligiblePreview() {
|
||||
TangemThemePreviewRedesign {
|
||||
DepositContent(
|
||||
state = previewState(shouldShowTermsAndConditions = true),
|
||||
modifier = Modifier.background(TangemTheme.colors3.bg.secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun DepositAvailablePreview() {
|
||||
TangemThemePreviewRedesign {
|
||||
DepositContent(
|
||||
state = previewState(shouldShowTermsAndConditions = false),
|
||||
modifier = Modifier.background(TangemTheme.colors3.bg.secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue