Updated on 2026-08-14
This commit is contained in:
parent
0c7586106e
commit
94ded5dcca
11 changed files with 621 additions and 9 deletions
|
|
@ -115,10 +115,17 @@
|
||||||
<string name="address_book_enter_address">Enter address</string>
|
<string name="address_book_enter_address">Enter address</string>
|
||||||
<string name="address_book_invalid_address_error">Invalid address</string>
|
<string name="address_book_invalid_address_error">Invalid address</string>
|
||||||
<string name="address_book_keep_editing">Keep editing</string>
|
<string name="address_book_keep_editing">Keep editing</string>
|
||||||
|
<string name="address_book_max_networks_alert_description">You can not create more than 20 addresses. Delete one to add new.</string>
|
||||||
|
<string name="address_book_max_networks_alert_title">Can\'t add new address </string>
|
||||||
|
<string name="address_book_name_empty_error">Contact name is required</string>
|
||||||
|
<string name="address_book_name_invalid_chars_error">Contact name contains invalid characters</string>
|
||||||
|
<string name="address_book_name_max_chars_error">Contact name must not exceed 50 characters</string>
|
||||||
|
<string name="address_book_name_taken_error">That name is already taken on this wallet</string>
|
||||||
<string name="address_book_new_contact">New contact</string>
|
<string name="address_book_new_contact">New contact</string>
|
||||||
<string name="address_book_no_contacts">No contacts yet</string>
|
<string name="address_book_no_contacts">No contacts yet</string>
|
||||||
<string name="address_book_no_contacts_description">Contacts added will appear here</string>
|
<string name="address_book_no_contacts_description">Contacts added will appear here</string>
|
||||||
<string name="address_book_remove_address">Remove address</string>
|
<string name="address_book_remove_address">Remove address</string>
|
||||||
|
<string name="address_book_save_to_wallet_title">Save to Wallet</string>
|
||||||
<string name="address_book_save_wallet_to_description">This contact will be linked to this wallet’s address book.</string>
|
<string name="address_book_save_wallet_to_description">This contact will be linked to this wallet’s address book.</string>
|
||||||
<string name="address_book_select_network">Select network</string>
|
<string name="address_book_select_network">Select network</string>
|
||||||
<string name="address_book_title">Address book</string>
|
<string name="address_book_title">Address book</string>
|
||||||
|
|
@ -352,6 +359,7 @@
|
||||||
<string name="common_get_token">Get token</string>
|
<string name="common_get_token">Get token</string>
|
||||||
<string name="common_go_to_provider">Go to provider</string>
|
<string name="common_go_to_provider">Go to provider</string>
|
||||||
<string name="common_go_to_token">Go to token</string>
|
<string name="common_go_to_token">Go to token</string>
|
||||||
|
<string name="common_go_to_verification">Go to verification</string>
|
||||||
<string name="common_got_it">Got it</string>
|
<string name="common_got_it">Got it</string>
|
||||||
<string name="common_hide">Hide</string>
|
<string name="common_hide">Hide</string>
|
||||||
<string name="common_hold_to">Hold to %s</string>
|
<string name="common_hold_to">Hold to %s</string>
|
||||||
|
|
@ -732,6 +740,8 @@
|
||||||
<string name="hardware_wallet_key_feature_title">Key Generation</string>
|
<string name="hardware_wallet_key_feature_title">Key Generation</string>
|
||||||
<string name="hardware_wallet_security_feature_description">All cryptographic operations happen inside the secure chip, certified against cloning and physical tampering.</string>
|
<string name="hardware_wallet_security_feature_description">All cryptographic operations happen inside the secure chip, certified against cloning and physical tampering.</string>
|
||||||
<string name="hardware_wallet_security_feature_title">Hardware-Level Security</string>
|
<string name="hardware_wallet_security_feature_title">Hardware-Level Security</string>
|
||||||
|
<string name="high_fee_warning_description">Network activity is high. You can continue now or try again later when fees may be lower.</string>
|
||||||
|
<string name="high_fee_warning_title">Network fee is higher than usual</string>
|
||||||
<string name="home_button_add_existing_wallet">Add Existing Wallet</string>
|
<string name="home_button_add_existing_wallet">Add Existing Wallet</string>
|
||||||
<string name="home_button_create_new_wallet">Create New Wallet</string>
|
<string name="home_button_create_new_wallet">Create New Wallet</string>
|
||||||
<string name="home_button_order">Order Tangem</string>
|
<string name="home_button_order">Order Tangem</string>
|
||||||
|
|
@ -1402,6 +1412,7 @@
|
||||||
<string name="send_extras_hint_memo">Memo</string>
|
<string name="send_extras_hint_memo">Memo</string>
|
||||||
<string name="send_fee_unreachable_error_text">Check your network connection</string>
|
<string name="send_fee_unreachable_error_text">Check your network connection</string>
|
||||||
<string name="send_fee_unreachable_error_title">Network fee info unreachable</string>
|
<string name="send_fee_unreachable_error_title">Network fee info unreachable</string>
|
||||||
|
<string name="send_from_account_in_wallet_name">from %1$s in %2$s</string>
|
||||||
<string name="send_from_title">You send</string>
|
<string name="send_from_title">You send</string>
|
||||||
<string name="send_from_wallet_name">From %s</string>
|
<string name="send_from_wallet_name">From %s</string>
|
||||||
<string name="send_gas_limit">Gas limit</string>
|
<string name="send_gas_limit">Gas limit</string>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||||
import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase
|
import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase
|
||||||
|
import com.tangem.domain.virtualaccount.model.VirtualAccountEligibility
|
||||||
|
import com.tangem.domain.virtualaccount.usecase.GetVirtualAccountEligibilityUseCase
|
||||||
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
|
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
|
||||||
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
|
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
|
||||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||||
|
|
@ -27,12 +29,7 @@ import com.tangem.features.details.utils.ItemsBuilder
|
||||||
import com.tangem.features.details.utils.SocialsBuilder
|
import com.tangem.features.details.utils.SocialsBuilder
|
||||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||||
import com.tangem.utils.info.AppInfoProvider
|
import com.tangem.utils.info.AppInfoProvider
|
||||||
import io.mockk.coEvery
|
import io.mockk.*
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.mockkObject
|
|
||||||
import io.mockk.slot
|
|
||||||
import io.mockk.unmockkObject
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
|
@ -66,6 +63,7 @@ internal abstract class DetailsModelTestBase {
|
||||||
protected val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase = mockk()
|
protected val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase = mockk()
|
||||||
protected val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxUnitFun = true)
|
protected val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxUnitFun = true)
|
||||||
protected val tangemPayEligibilityManager: TangemPayEligibilityManager = mockk()
|
protected val tangemPayEligibilityManager: TangemPayEligibilityManager = mockk()
|
||||||
|
protected val getVirtualAccountEligibilityUseCase: GetVirtualAccountEligibilityUseCase = mockk()
|
||||||
|
|
||||||
// Captured from itemsBuilder.buildAll(...) so the feature buttons can be driven.
|
// Captured from itemsBuilder.buildAll(...) so the feature buttons can be driven.
|
||||||
protected val wcSlot = slot<Boolean>()
|
protected val wcSlot = slot<Boolean>()
|
||||||
|
|
@ -89,6 +87,7 @@ internal abstract class DetailsModelTestBase {
|
||||||
every { appInfoProvider.appVersion } returns "1.2.3"
|
every { appInfoProvider.appVersion } returns "1.2.3"
|
||||||
every { appInfoProvider.appVersionCode } returns 456
|
every { appInfoProvider.appVersionCode } returns 456
|
||||||
coEvery { tangemPayEligibilityManager.getEligibleWallets(any(), any()) } returns emptyList()
|
coEvery { tangemPayEligibilityManager.getEligibleWallets(any(), any()) } returns emptyList()
|
||||||
|
coEvery { getVirtualAccountEligibilityUseCase(any()) } returns VirtualAccountEligibility.NotAvailable
|
||||||
|
|
||||||
every {
|
every {
|
||||||
itemsBuilder.buildAll(
|
itemsBuilder.buildAll(
|
||||||
|
|
@ -128,6 +127,7 @@ internal abstract class DetailsModelTestBase {
|
||||||
generateBuyTangemCardLinkUseCase = generateBuyTangemCardLinkUseCase,
|
generateBuyTangemCardLinkUseCase = generateBuyTangemCardLinkUseCase,
|
||||||
analyticsEventHandler = analyticsEventHandler,
|
analyticsEventHandler = analyticsEventHandler,
|
||||||
tangemPayEligibilityManager = tangemPayEligibilityManager,
|
tangemPayEligibilityManager = tangemPayEligibilityManager,
|
||||||
|
getVirtualAccountEligibilityUseCase = getVirtualAccountEligibilityUseCase,
|
||||||
)
|
)
|
||||||
|
|
||||||
protected fun stubBuildAllReturns(list: ImmutableList<DetailsItemUM>) {
|
protected fun stubBuildAllReturns(list: ImmutableList<DetailsItemUM>) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
plugins {
|
plugins {
|
||||||
alias(deps.plugins.android.library)
|
alias(deps.plugins.android.library)
|
||||||
alias(deps.plugins.kotlin.android)
|
alias(deps.plugins.kotlin.android)
|
||||||
|
alias(deps.plugins.kotlin.serialization)
|
||||||
alias(deps.plugins.kotlin.kapt)
|
alias(deps.plugins.kotlin.kapt)
|
||||||
alias(deps.plugins.hilt.android)
|
alias(deps.plugins.hilt.android)
|
||||||
id("configuration")
|
id("configuration")
|
||||||
|
|
@ -14,12 +15,15 @@ dependencies {
|
||||||
/** Core */
|
/** Core */
|
||||||
implementation(projects.core.configToggles)
|
implementation(projects.core.configToggles)
|
||||||
implementation(projects.core.decompose)
|
implementation(projects.core.decompose)
|
||||||
|
implementation(projects.core.navigation)
|
||||||
implementation(projects.core.res)
|
implementation(projects.core.res)
|
||||||
implementation(projects.core.ui)
|
implementation(projects.core.ui)
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
|
|
||||||
/** Domain */
|
/** Domain */
|
||||||
implementation(projects.domain.models)
|
implementation(projects.domain.models)
|
||||||
|
implementation(projects.domain.feedback)
|
||||||
|
implementation(projects.domain.feedback.models)
|
||||||
|
|
||||||
/** Features */
|
/** Features */
|
||||||
implementation(projects.features.virtualAccounts.details.api)
|
implementation(projects.features.virtualAccounts.details.api)
|
||||||
|
|
@ -31,6 +35,10 @@ dependencies {
|
||||||
implementation(deps.compose.ui.tooling)
|
implementation(deps.compose.ui.tooling)
|
||||||
implementation(deps.decompose.ext.compose)
|
implementation(deps.decompose.ext.compose)
|
||||||
|
|
||||||
|
/** Other */
|
||||||
|
implementation(deps.arrow.core)
|
||||||
|
implementation(deps.kotlin.immutable.collections)
|
||||||
|
|
||||||
/** DI */
|
/** DI */
|
||||||
implementation(deps.hilt.android)
|
implementation(deps.hilt.android)
|
||||||
kapt(deps.hilt.kapt)
|
kapt(deps.hilt.kapt)
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,56 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.arkivanov.decompose.ComponentContext
|
||||||
|
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||||
|
import com.arkivanov.decompose.router.slot.childSlot
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
|
import com.tangem.core.decompose.context.childByContext
|
||||||
import com.tangem.core.decompose.model.getOrCreateModel
|
import com.tangem.core.decompose.model.getOrCreateModel
|
||||||
|
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||||
import com.tangem.features.virtualaccount.details.component.VirtualAccountMainComponent
|
import com.tangem.features.virtualaccount.details.component.VirtualAccountMainComponent
|
||||||
|
import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsBottomSheetComponent
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
|
||||||
internal class DefaultVirtualAccountMainComponent @AssistedInject constructor(
|
internal class DefaultVirtualAccountMainComponent @AssistedInject constructor(
|
||||||
@Assisted appComponentContext: AppComponentContext,
|
@Assisted appComponentContext: AppComponentContext,
|
||||||
@Assisted params: VirtualAccountMainComponent.Params,
|
@Assisted private val params: VirtualAccountMainComponent.Params,
|
||||||
) : VirtualAccountMainComponent, AppComponentContext by appComponentContext {
|
) : VirtualAccountMainComponent, AppComponentContext by appComponentContext {
|
||||||
|
|
||||||
private val model: VirtualAccountMainModel = getOrCreateModel(params = params)
|
private val model: VirtualAccountMainModel = getOrCreateModel(params = params)
|
||||||
|
|
||||||
|
private val bottomSheetSlot = childSlot(
|
||||||
|
source = model.bottomSheetNavigation,
|
||||||
|
serializer = VirtualAccountMainNavigationBottomSheetConfig.serializer(),
|
||||||
|
handleBackButton = false,
|
||||||
|
childFactory = ::bottomSheetChild,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(modifier: Modifier) {
|
override fun Content(modifier: Modifier) {
|
||||||
val state by model.uiState.collectAsStateWithLifecycle()
|
val state by model.uiState.collectAsStateWithLifecycle()
|
||||||
|
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||||
VirtualAccountMainScreen(state = state, modifier = modifier)
|
VirtualAccountMainScreen(state = state, modifier = modifier)
|
||||||
|
bottomSheet.child?.instance?.BottomSheet()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bottomSheetChild(
|
||||||
|
config: VirtualAccountMainNavigationBottomSheetConfig,
|
||||||
|
componentContext: ComponentContext,
|
||||||
|
): ComposableBottomSheetComponent {
|
||||||
|
return when (config) {
|
||||||
|
is VirtualAccountMainNavigationBottomSheetConfig.AddFunds -> VirtualAccountAddFundsBottomSheetComponent(
|
||||||
|
appComponentContext = childByContext(componentContext),
|
||||||
|
params = VirtualAccountAddFundsBottomSheetComponent.Params(
|
||||||
|
userWalletId = params.userWalletId,
|
||||||
|
listener = model,
|
||||||
|
requisites = config.requisites,
|
||||||
|
dailyDepositLimit = config.dailyDepositLimit,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package com.tangem.features.virtualaccount.main
|
package com.tangem.features.virtualaccount.main
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||||
|
import com.arkivanov.decompose.router.slot.activate
|
||||||
|
import com.arkivanov.decompose.router.slot.dismiss
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
|
|
@ -9,6 +12,8 @@ import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.features.virtualaccount.details.component.VirtualAccountMainComponent
|
import com.tangem.features.virtualaccount.details.component.VirtualAccountMainComponent
|
||||||
import com.tangem.features.virtualaccount.details.impl.R
|
import com.tangem.features.virtualaccount.details.impl.R
|
||||||
|
import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsBottomSheetComponent
|
||||||
|
import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsListener
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
@ -20,16 +25,22 @@ internal class VirtualAccountMainModel @Inject constructor(
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val router: Router,
|
private val router: Router,
|
||||||
) : Model() {
|
) : Model(), VirtualAccountAddFundsListener {
|
||||||
|
|
||||||
@Suppress("UnusedPrivateProperty")
|
@Suppress("UnusedPrivateProperty")
|
||||||
private val params = paramsContainer.require<VirtualAccountMainComponent.Params>()
|
private val params = paramsContainer.require<VirtualAccountMainComponent.Params>()
|
||||||
|
|
||||||
|
val bottomSheetNavigation: SlotNavigation<VirtualAccountMainNavigationBottomSheetConfig> = SlotNavigation()
|
||||||
|
|
||||||
val uiState: StateFlow<VirtualAccountMainUM>
|
val uiState: StateFlow<VirtualAccountMainUM>
|
||||||
field = MutableStateFlow(
|
field = MutableStateFlow(
|
||||||
createInitialState(),
|
createInitialState(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override fun onAddFundsDismiss() {
|
||||||
|
bottomSheetNavigation.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
private fun createInitialState(): VirtualAccountMainUM = VirtualAccountMainUM(
|
private fun createInitialState(): VirtualAccountMainUM = VirtualAccountMainUM(
|
||||||
title = resourceReference(R.string.virtual_account_title),
|
title = resourceReference(R.string.virtual_account_title),
|
||||||
subtitle = resourceReference(R.string.tangempay_usdc_on_polygon_network),
|
subtitle = resourceReference(R.string.tangempay_usdc_on_polygon_network),
|
||||||
|
|
@ -40,7 +51,63 @@ internal class VirtualAccountMainModel @Inject constructor(
|
||||||
isBalanceHidden = false,
|
isBalanceHidden = false,
|
||||||
onBackClick = { router.pop() },
|
onBackClick = { router.pop() },
|
||||||
onMenuClick = {},
|
onMenuClick = {},
|
||||||
onAddFundsClick = {},
|
onAddFundsClick = ::onAddFundsClick,
|
||||||
onSendClick = {},
|
onSendClick = {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun buildRequisites(details: VirtualAccountDepositDetails) = listOf(
|
||||||
|
VirtualAccountAddFundsBottomSheetComponent.RequisitesRow(
|
||||||
|
title = stringReference("Beneficiary name and address"),
|
||||||
|
titleForShare = "Beneficiary name and address",
|
||||||
|
value = "${details.beneficiaryName}\n${details.beneficiaryAddress}",
|
||||||
|
),
|
||||||
|
VirtualAccountAddFundsBottomSheetComponent.RequisitesRow(
|
||||||
|
title = stringReference("Bank name and address"),
|
||||||
|
titleForShare = "Bank name and address",
|
||||||
|
value = "${details.bankName}\n${details.bankAddress}",
|
||||||
|
),
|
||||||
|
VirtualAccountAddFundsBottomSheetComponent.RequisitesRow(
|
||||||
|
title = stringReference("Account number"),
|
||||||
|
titleForShare = "Account number",
|
||||||
|
value = details.accountNumber,
|
||||||
|
),
|
||||||
|
VirtualAccountAddFundsBottomSheetComponent.RequisitesRow(
|
||||||
|
title = stringReference("Routing number"),
|
||||||
|
titleForShare = "Routing number",
|
||||||
|
value = details.routingNumber,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun onAddFundsClick() {
|
||||||
|
val details = getDepositDetails()
|
||||||
|
bottomSheetNavigation.activate(
|
||||||
|
VirtualAccountMainNavigationBottomSheetConfig.AddFunds(
|
||||||
|
requisites = buildRequisites(details),
|
||||||
|
dailyDepositLimit = details.dailyDepositLimit,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO v_rodionov: HARDCODE - get this data from backend
|
||||||
|
private fun getDepositDetails(): VirtualAccountDepositDetails {
|
||||||
|
return VirtualAccountDepositDetails(
|
||||||
|
beneficiaryName = "Ivan Ivanov",
|
||||||
|
beneficiaryAddress = "18, Rue Rubens 20, Paris, Ile-de-France 75013, US",
|
||||||
|
bankName = "SSB Bank",
|
||||||
|
bankAddress = "8700 Perry Highway, Pittsburgh, PA 15237, US",
|
||||||
|
accountNumber = "707613210122",
|
||||||
|
routingNumber = "043087080",
|
||||||
|
dailyDepositLimit = "$10,000",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class VirtualAccountDepositDetails(
|
||||||
|
val beneficiaryName: String,
|
||||||
|
val beneficiaryAddress: String,
|
||||||
|
val bankName: String,
|
||||||
|
val bankAddress: String,
|
||||||
|
val accountNumber: String,
|
||||||
|
val routingNumber: String,
|
||||||
|
val dailyDepositLimit: String,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.tangem.features.virtualaccount.main
|
||||||
|
|
||||||
|
import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsBottomSheetComponent.RequisitesRow
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
internal sealed interface VirtualAccountMainNavigationBottomSheetConfig {
|
||||||
|
data class AddFunds(
|
||||||
|
val requisites: List<RequisitesRow>,
|
||||||
|
val dailyDepositLimit: String,
|
||||||
|
) : VirtualAccountMainNavigationBottomSheetConfig
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,328 @@
|
||||||
|
package com.tangem.features.virtualaccount.main.addfunds
|
||||||
|
|
||||||
|
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.style.TextAlign
|
||||||
|
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.image.TangemIconUM
|
||||||
|
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||||
|
import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
||||||
|
import com.tangem.core.ui.ds2.button.Close
|
||||||
|
import com.tangem.core.ui.ds2.button.TangemButton
|
||||||
|
import com.tangem.core.ui.ds2.row.*
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
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.TangemThemePreviewRedesign
|
||||||
|
import com.tangem.core.ui.res.generated.icons.Icons
|
||||||
|
import com.tangem.core.ui.res.generated.icons.ic_copy_24
|
||||||
|
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.virtualaccount.details.impl.R
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import com.tangem.core.ui.R as CoreUiR
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun VirtualAccountAddFundsBottomSheet(state: VirtualAccountAddFundsUM) {
|
||||||
|
val title = stringReference("Account details")
|
||||||
|
.takeIf { state.content is VirtualAccountAddFundsUM.Content.Details }
|
||||||
|
|
||||||
|
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 = title,
|
||||||
|
endContent = { TangemButton.Close(onClick = state.onDismiss) },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = { _ ->
|
||||||
|
when (val content = state.content) {
|
||||||
|
is VirtualAccountAddFundsUM.Content.Intro -> IntroContent(content)
|
||||||
|
is VirtualAccountAddFundsUM.Content.Details -> DetailsContent(content)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun IntroContent(content: VirtualAccountAddFundsUM.Content.Intro, 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("Received USD will be converted to USDC by 1:1 rate"),
|
||||||
|
modifier = Modifier.padding(top = TangemTheme.dimens2.x8),
|
||||||
|
)
|
||||||
|
SubtitleText(
|
||||||
|
text = stringReference("It might take 1-3 days to receive the money"),
|
||||||
|
modifier = Modifier.padding(top = TangemTheme.dimens2.x2),
|
||||||
|
)
|
||||||
|
InfoNotification(
|
||||||
|
title = stringReference("Only ACH and domestic wire transfers are available"),
|
||||||
|
subtitle = stringReference("SWIFT won't pass"),
|
||||||
|
modifier = Modifier.padding(top = TangemTheme.dimens2.x6),
|
||||||
|
)
|
||||||
|
TangemButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = TangemTheme.dimens2.x4),
|
||||||
|
text = stringReference("Show details"),
|
||||||
|
variant = TangemButton.Variant.Primary,
|
||||||
|
size = TangemButton.Size.X12,
|
||||||
|
onClick = content.onShowDetailsClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DetailsContent(content: VirtualAccountAddFundsUM.Content.Details, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = TangemTheme.dimens2.x4),
|
||||||
|
) {
|
||||||
|
content.items.forEachIndexed { index, item ->
|
||||||
|
CopyableRow(
|
||||||
|
item = item,
|
||||||
|
divider = index != content.items.lastIndex,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
InfoNotification(
|
||||||
|
title = stringReference("Available to deposit per day: ${content.dailyLimit}"),
|
||||||
|
subtitle = stringReference("Limit is resetting every day"),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = TangemTheme.dimens2.x4)
|
||||||
|
.padding(top = TangemTheme.dimens2.x3),
|
||||||
|
)
|
||||||
|
TangemButton(
|
||||||
|
text = resourceReference(R.string.common_share),
|
||||||
|
variant = TangemButton.Variant.Primary,
|
||||||
|
size = TangemButton.Size.X12,
|
||||||
|
onClick = content.onShareClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = TangemTheme.dimens2.x4)
|
||||||
|
.padding(top = TangemTheme.dimens2.x4),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CopyableRow(item: VirtualAccountAddFundsUM.DetailItem, divider: Boolean, modifier: Modifier = Modifier) {
|
||||||
|
TangemRow(
|
||||||
|
modifier = modifier,
|
||||||
|
divider = divider,
|
||||||
|
contentLead = TangemRowContentLead.Start,
|
||||||
|
verticalAlignment = TangemRowVerticalAlignment.Center,
|
||||||
|
titleSlot = {
|
||||||
|
TangemRowText(
|
||||||
|
text = item.label,
|
||||||
|
role = TangemRowTextRole.Subtitle,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
subtitleSlot = {
|
||||||
|
TangemRowText(
|
||||||
|
text = item.value,
|
||||||
|
role = TangemRowTextRole.Title,
|
||||||
|
maxLines = Int.MAX_VALUE,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
endSlot = {
|
||||||
|
TangemButton(
|
||||||
|
iconStart = TangemIconUM.Icon(imageVector = Icons.ic_copy_24),
|
||||||
|
onClick = item.onCopyClick,
|
||||||
|
size = TangemButton.Size.X9,
|
||||||
|
variant = TangemButton.Variant.Ghost,
|
||||||
|
contentDescription = item.label.resolveReference(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InfoNotification(title: TextReference, subtitle: 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,
|
||||||
|
)
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5)) {
|
||||||
|
Text(
|
||||||
|
text = title.resolveReference(),
|
||||||
|
style = TangemTheme.typography3.subheading.medium,
|
||||||
|
color = TangemTheme.colors3.text.primary,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = subtitle.resolveReference(),
|
||||||
|
style = TangemTheme.typography3.caption.medium,
|
||||||
|
color = TangemTheme.colors3.text.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun VirtualAccountAddFundsIntroPreview() {
|
||||||
|
TangemThemePreviewRedesign {
|
||||||
|
IntroContent(
|
||||||
|
content = VirtualAccountAddFundsUM.Content.Intro(
|
||||||
|
onShowDetailsClick = {},
|
||||||
|
),
|
||||||
|
modifier = Modifier.background(TangemTheme.colors3.bg.secondary),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun VirtualAccountAddFundsDetailsPreview() {
|
||||||
|
TangemThemePreviewRedesign {
|
||||||
|
DetailsContent(
|
||||||
|
content = VirtualAccountAddFundsUM.Content.Details(
|
||||||
|
items = persistentListOf(
|
||||||
|
VirtualAccountAddFundsUM.DetailItem(
|
||||||
|
label = stringReference("Beneficiary name and address"),
|
||||||
|
value = "Ivan Ivanov\n18, Rue Rubens 20, Paris, Ile-de-France 75013, US",
|
||||||
|
onCopyClick = {},
|
||||||
|
),
|
||||||
|
VirtualAccountAddFundsUM.DetailItem(
|
||||||
|
label = stringReference("Account number"),
|
||||||
|
value = "707613210122",
|
||||||
|
onCopyClick = {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dailyLimit = "$10,000",
|
||||||
|
onShareClick = {},
|
||||||
|
),
|
||||||
|
modifier = Modifier.background(TangemTheme.colors3.bg.secondary),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.tangem.features.virtualaccount.main.addfunds
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
|
import com.tangem.core.decompose.model.getOrCreateModel
|
||||||
|
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
|
||||||
|
internal class VirtualAccountAddFundsBottomSheetComponent(
|
||||||
|
appComponentContext: AppComponentContext,
|
||||||
|
params: Params,
|
||||||
|
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
|
||||||
|
|
||||||
|
private val model: VirtualAccountAddFundsModel = getOrCreateModel(params = params)
|
||||||
|
|
||||||
|
override fun dismiss() {
|
||||||
|
model.onDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun BottomSheet() {
|
||||||
|
val state by model.uiState.collectAsStateWithLifecycle()
|
||||||
|
VirtualAccountAddFundsBottomSheet(state = state)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Params(
|
||||||
|
val userWalletId: UserWalletId,
|
||||||
|
val requisites: List<RequisitesRow>,
|
||||||
|
val dailyDepositLimit: String,
|
||||||
|
val listener: VirtualAccountAddFundsListener,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class RequisitesRow(
|
||||||
|
val title: TextReference,
|
||||||
|
val titleForShare: String,
|
||||||
|
val value: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface VirtualAccountAddFundsListener {
|
||||||
|
fun onAddFundsDismiss()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package com.tangem.features.virtualaccount.main.addfunds
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.util.fastForEach
|
||||||
|
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.share.ShareManager
|
||||||
|
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
@ModelScoped
|
||||||
|
internal class VirtualAccountAddFundsModel @Inject constructor(
|
||||||
|
paramsContainer: ParamsContainer,
|
||||||
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
private val clipboardManager: ClipboardManager,
|
||||||
|
private val shareManager: ShareManager,
|
||||||
|
) : Model() {
|
||||||
|
|
||||||
|
private val params = paramsContainer.require<VirtualAccountAddFundsBottomSheetComponent.Params>()
|
||||||
|
|
||||||
|
val uiState: StateFlow<VirtualAccountAddFundsUM>
|
||||||
|
field = MutableStateFlow(
|
||||||
|
VirtualAccountAddFundsUM(
|
||||||
|
onDismiss = ::onDismiss,
|
||||||
|
content = VirtualAccountAddFundsUM.Content.Intro(
|
||||||
|
onShowDetailsClick = { showDetailsContent() },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun onDismiss() {
|
||||||
|
params.listener.onAddFundsDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDetailsContent() {
|
||||||
|
uiState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
content = VirtualAccountAddFundsUM.Content.Details(
|
||||||
|
items = params.requisites
|
||||||
|
.map { detailItem(label = it.title, value = it.value) }
|
||||||
|
.toImmutableList(),
|
||||||
|
dailyLimit = params.dailyDepositLimit,
|
||||||
|
onShareClick = { shareManager.shareText(buildShareText()) },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun detailItem(label: TextReference, value: String) = VirtualAccountAddFundsUM.DetailItem(
|
||||||
|
label = label,
|
||||||
|
value = value,
|
||||||
|
onCopyClick = { clipboardManager.setText(text = value, isSensitive = true) },
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun buildShareText(): String {
|
||||||
|
return buildString {
|
||||||
|
params.requisites.fastForEach { item ->
|
||||||
|
appendLine("${item.titleForShare}: ${item.value}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.tangem.features.virtualaccount.main.addfunds
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
internal data class VirtualAccountAddFundsUM(
|
||||||
|
val onDismiss: () -> Unit,
|
||||||
|
val content: Content,
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
sealed interface Content {
|
||||||
|
|
||||||
|
data class Intro(
|
||||||
|
val onShowDetailsClick: () -> Unit,
|
||||||
|
) : Content
|
||||||
|
|
||||||
|
data class Details(
|
||||||
|
val items: ImmutableList<DetailItem>,
|
||||||
|
val dailyLimit: String,
|
||||||
|
val onShareClick: () -> Unit,
|
||||||
|
) : Content
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class DetailItem(
|
||||||
|
val label: TextReference,
|
||||||
|
val value: String,
|
||||||
|
val onCopyClick: () -> Unit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.virtualaccount.main.di
|
||||||
import com.tangem.core.decompose.di.ModelComponent
|
import com.tangem.core.decompose.di.ModelComponent
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.features.virtualaccount.main.VirtualAccountMainModel
|
import com.tangem.features.virtualaccount.main.VirtualAccountMainModel
|
||||||
|
import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsModel
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -17,4 +18,9 @@ internal interface VirtualAccountMainModelModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ClassKey(VirtualAccountMainModel::class)
|
@ClassKey(VirtualAccountMainModel::class)
|
||||||
fun bindVirtualAccountMainModel(model: VirtualAccountMainModel): Model
|
fun bindVirtualAccountMainModel(model: VirtualAccountMainModel): Model
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ClassKey(VirtualAccountAddFundsModel::class)
|
||||||
|
fun bindVirtualAccountAddFundsModel(model: VirtualAccountAddFundsModel): Model
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue