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_invalid_address_error">Invalid address</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_no_contacts">No contacts yet</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_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_select_network">Select network</string>
|
||||
<string name="address_book_title">Address book</string>
|
||||
|
|
@ -352,6 +359,7 @@
|
|||
<string name="common_get_token">Get token</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_verification">Go to verification</string>
|
||||
<string name="common_got_it">Got it</string>
|
||||
<string name="common_hide">Hide</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_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="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_create_new_wallet">Create New Wallet</string>
|
||||
<string name="home_button_order">Order Tangem</string>
|
||||
|
|
@ -1402,6 +1412,7 @@
|
|||
<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_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_wallet_name">From %s</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.pay.TangemPayEligibilityManager
|
||||
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.wallets.usecase.GenerateBuyTangemCardLinkUseCase
|
||||
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.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.slot
|
||||
import io.mockk.unmockkObject
|
||||
import io.mockk.*
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -66,6 +63,7 @@ internal abstract class DetailsModelTestBase {
|
|||
protected val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase = mockk()
|
||||
protected val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxUnitFun = true)
|
||||
protected val tangemPayEligibilityManager: TangemPayEligibilityManager = mockk()
|
||||
protected val getVirtualAccountEligibilityUseCase: GetVirtualAccountEligibilityUseCase = mockk()
|
||||
|
||||
// Captured from itemsBuilder.buildAll(...) so the feature buttons can be driven.
|
||||
protected val wcSlot = slot<Boolean>()
|
||||
|
|
@ -89,6 +87,7 @@ internal abstract class DetailsModelTestBase {
|
|||
every { appInfoProvider.appVersion } returns "1.2.3"
|
||||
every { appInfoProvider.appVersionCode } returns 456
|
||||
coEvery { tangemPayEligibilityManager.getEligibleWallets(any(), any()) } returns emptyList()
|
||||
coEvery { getVirtualAccountEligibilityUseCase(any()) } returns VirtualAccountEligibility.NotAvailable
|
||||
|
||||
every {
|
||||
itemsBuilder.buildAll(
|
||||
|
|
@ -128,6 +127,7 @@ internal abstract class DetailsModelTestBase {
|
|||
generateBuyTangemCardLinkUseCase = generateBuyTangemCardLinkUseCase,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
tangemPayEligibilityManager = tangemPayEligibilityManager,
|
||||
getVirtualAccountEligibilityUseCase = getVirtualAccountEligibilityUseCase,
|
||||
)
|
||||
|
||||
protected fun stubBuildAllReturns(list: ImmutableList<DetailsItemUM>) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
|
|
@ -14,12 +15,15 @@ dependencies {
|
|||
/** Core */
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.feedback.models)
|
||||
|
||||
/** Features */
|
||||
implementation(projects.features.virtualAccounts.details.api)
|
||||
|
|
@ -31,6 +35,10 @@ dependencies {
|
|||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
|
||||
/** Other */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
|
|
|||
|
|
@ -4,24 +4,56 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
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.childByContext
|
||||
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.main.addfunds.VirtualAccountAddFundsBottomSheetComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultVirtualAccountMainComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: VirtualAccountMainComponent.Params,
|
||||
@Assisted private val params: VirtualAccountMainComponent.Params,
|
||||
) : VirtualAccountMainComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: VirtualAccountMainModel = getOrCreateModel(params = params)
|
||||
|
||||
private val bottomSheetSlot = childSlot(
|
||||
source = model.bottomSheetNavigation,
|
||||
serializer = VirtualAccountMainNavigationBottomSheetConfig.serializer(),
|
||||
handleBackButton = false,
|
||||
childFactory = ::bottomSheetChild,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.features.virtualaccount.main
|
||||
|
||||
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.model.Model
|
||||
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.features.virtualaccount.details.component.VirtualAccountMainComponent
|
||||
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 kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -20,16 +25,22 @@ internal class VirtualAccountMainModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
) : Model() {
|
||||
) : Model(), VirtualAccountAddFundsListener {
|
||||
|
||||
@Suppress("UnusedPrivateProperty")
|
||||
private val params = paramsContainer.require<VirtualAccountMainComponent.Params>()
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<VirtualAccountMainNavigationBottomSheetConfig> = SlotNavigation()
|
||||
|
||||
val uiState: StateFlow<VirtualAccountMainUM>
|
||||
field = MutableStateFlow(
|
||||
createInitialState(),
|
||||
)
|
||||
|
||||
override fun onAddFundsDismiss() {
|
||||
bottomSheetNavigation.dismiss()
|
||||
}
|
||||
|
||||
private fun createInitialState(): VirtualAccountMainUM = VirtualAccountMainUM(
|
||||
title = resourceReference(R.string.virtual_account_title),
|
||||
subtitle = resourceReference(R.string.tangempay_usdc_on_polygon_network),
|
||||
|
|
@ -40,7 +51,63 @@ internal class VirtualAccountMainModel @Inject constructor(
|
|||
isBalanceHidden = false,
|
||||
onBackClick = { router.pop() },
|
||||
onMenuClick = {},
|
||||
onAddFundsClick = {},
|
||||
onAddFundsClick = ::onAddFundsClick,
|
||||
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.model.Model
|
||||
import com.tangem.features.virtualaccount.main.VirtualAccountMainModel
|
||||
import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -17,4 +18,9 @@ internal interface VirtualAccountMainModelModule {
|
|||
@IntoMap
|
||||
@ClassKey(VirtualAccountMainModel::class)
|
||||
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