Updated on 2026-08-14
This commit is contained in:
commit
c32a5fa18d
711 changed files with 18558 additions and 4312 deletions
|
|
@ -21,4 +21,5 @@ dependencies {
|
|||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.account)
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.features.account
|
||||
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.account.Account
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface AccountSelectorComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(
|
||||
val onDismiss: () -> Unit,
|
||||
val accountsBalanceFetcher: AccountsBalanceFetcher,
|
||||
val controller: AccountSelectorController,
|
||||
)
|
||||
|
||||
interface Factory {
|
||||
fun create(appComponentContext: AppComponentContext, params: Params): AccountSelectorComponent
|
||||
}
|
||||
}
|
||||
|
||||
interface AccountSelectorController {
|
||||
val selectedAccount: StateFlow<Account?>
|
||||
fun selectAccount(account: Account?)
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.features.account
|
||||
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
|
|
@ -11,7 +11,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface AccountsBalanceFetcher {
|
||||
interface PortfolioFetcher {
|
||||
|
||||
val data: Flow<Data>
|
||||
|
||||
|
|
@ -21,12 +21,15 @@ interface AccountsBalanceFetcher {
|
|||
data class Data(
|
||||
val appCurrency: AppCurrency,
|
||||
val isBalanceHidden: Boolean,
|
||||
val balances: Map<UserWallet, Map<Account, AccountBalance>>,
|
||||
val balances: Map<UserWallet, PortfolioBalance>,
|
||||
)
|
||||
|
||||
data class AccountBalance(
|
||||
val balance: Lce<TokenListError, TotalFiatBalance>,
|
||||
)
|
||||
data class PortfolioBalance(
|
||||
val walletBalance: Lce<TokenListError, TotalFiatBalance>,
|
||||
val accountsBalance: AccountStatusList,
|
||||
) {
|
||||
val userWallet: UserWallet get() = accountsBalance.userWallet
|
||||
}
|
||||
|
||||
sealed interface Mode {
|
||||
data class All(val onlyMultiCurrency: Boolean) : Mode
|
||||
|
|
@ -34,6 +37,6 @@ interface AccountsBalanceFetcher {
|
|||
}
|
||||
|
||||
interface Factory {
|
||||
fun create(mode: Mode, scope: CoroutineScope): AccountsBalanceFetcher
|
||||
fun create(mode: Mode, scope: CoroutineScope): PortfolioFetcher
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.features.account
|
||||
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface PortfolioSelectorComponent : ComposableBottomSheetComponent, ComposableContentComponent {
|
||||
|
||||
val title: StateFlow<TextReference>
|
||||
|
||||
data class Params(
|
||||
val onDismiss: () -> Unit,
|
||||
val portfolioFetcher: PortfolioFetcher,
|
||||
val controller: PortfolioSelectorController,
|
||||
)
|
||||
|
||||
interface Factory {
|
||||
fun create(appComponentContext: AppComponentContext, params: Params): PortfolioSelectorComponent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if [isAccountMode] is false it's mean [selectedAccount] emit [AccountId] for Main account
|
||||
*/
|
||||
interface PortfolioSelectorController {
|
||||
val isAccountMode: Flow<Boolean>
|
||||
val selectedAccount: Flow<AccountId?>
|
||||
|
||||
fun selectAccount(accountId: AccountId?)
|
||||
fun selectedAccountWithData(portfolioFetcher: PortfolioFetcher): Flow<Pair<UserWallet, Account>?>
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ android {
|
|||
dependencies {
|
||||
/** Api */
|
||||
implementation(projects.features.account.api)
|
||||
implementation(projects.features.wallet.api)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.analytics)
|
||||
|
|
@ -29,6 +30,7 @@ dependencies {
|
|||
/** Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
|
|
@ -39,6 +41,9 @@ dependencies {
|
|||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(tangemDeps.card.core)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
private val umBuilder = AccountCreateEditUMBuilder(params)
|
||||
|
||||
val uiState: StateFlow<AccountCreateEditUM>
|
||||
field = MutableStateFlow(value = getInitialState())
|
||||
field = MutableStateFlow(value = getInitialState())
|
||||
|
||||
init {
|
||||
if (params is AccountCreateEditComponent.Params.Create) {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
package com.tangem.features.account.fetcher
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.account.AccountsBalanceFetcher
|
||||
import com.tangem.features.account.AccountsBalanceFetcher.*
|
||||
import com.tangem.features.account.PortfolioFetcher
|
||||
import com.tangem.features.account.PortfolioFetcher.*
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -20,14 +21,17 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
internal class DefaultAccountsBalanceFetcher @AssistedInject constructor(
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultPortfolioFetcher @AssistedInject constructor(
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val getWallets: GetWalletsUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
@Assisted mode: Mode,
|
||||
@Assisted private val scope: CoroutineScope,
|
||||
) : AccountsBalanceFetcher {
|
||||
) : PortfolioFetcher {
|
||||
|
||||
private val _mode = MutableStateFlow(mode)
|
||||
private val _data = MutableSharedFlow<Data>(
|
||||
|
|
@ -73,33 +77,22 @@ internal class DefaultAccountsBalanceFetcher @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun balancesForWallets(wallets: List<UserWallet>): Flow<Map<UserWallet, Map<Account, AccountBalance>>> =
|
||||
wallets.asFlow()
|
||||
.map { walletAccountsBalancesFlow(it) }
|
||||
.mapLatest { accountsBalances -> combine(accountsBalances) { pairs -> pairs.toMap() } }
|
||||
.flattenConcat()
|
||||
|
||||
private fun walletAccountsBalancesFlow(wallet: UserWallet): Flow<Pair<UserWallet, Map<Account, AccountBalance>>> =
|
||||
walletAccounts(wallet)
|
||||
.distinctUntilChanged()
|
||||
.map { list -> list.map(::accountBalanceFlow) }
|
||||
.mapLatest { balanceFlows -> combine(balanceFlows) { balances -> balances.toMap() } }
|
||||
.flattenConcat()
|
||||
.map { accountBalance -> wallet to accountBalance }
|
||||
|
||||
private fun walletAccounts(wallet: UserWallet): Flow<List<Account>> = flow {
|
||||
// todo account load accounts
|
||||
val accounts: List<Account> = Account.CryptoPortfolio
|
||||
.createMainAccount(wallet.walletId)
|
||||
.let(::listOf)
|
||||
emit(accounts)
|
||||
private fun balancesForWallets(wallets: List<UserWallet>): Flow<Map<UserWallet, PortfolioBalance>> {
|
||||
val balanceFlows = wallets.map { walletAccountsBalancesFlow(it) }
|
||||
return combine(balanceFlows) { pairs -> pairs.toMap() }
|
||||
}
|
||||
|
||||
private fun accountBalanceFlow(account: Account): Flow<Pair<Account, AccountBalance>> = flow {
|
||||
// todo account load balance
|
||||
val balance = AccountBalance(balance = Lce.Content(TotalFiatBalance.Loading))
|
||||
emit(account to balance)
|
||||
}
|
||||
private fun walletAccountsBalancesFlow(wallet: UserWallet): Flow<Pair<UserWallet, PortfolioBalance>> = combine(
|
||||
flow = accountStatusListFlow(wallet),
|
||||
flow2 = getWalletTotalBalanceUseCase(wallet.walletId),
|
||||
transform = { accountStatusList, walletBalance ->
|
||||
val portfolioBalance = PortfolioBalance(walletBalance, accountStatusList)
|
||||
wallet to portfolioBalance
|
||||
},
|
||||
)
|
||||
|
||||
private fun accountStatusListFlow(wallet: UserWallet): Flow<AccountStatusList> =
|
||||
singleAccountStatusListSupplier(SingleAccountStatusListProducer.Params(wallet.walletId))
|
||||
|
||||
private fun appCurrencyFlow(): Flow<AppCurrency> {
|
||||
return getSelectedAppCurrencyUseCase()
|
||||
|
|
@ -114,7 +107,7 @@ internal class DefaultAccountsBalanceFetcher @AssistedInject constructor(
|
|||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : AccountsBalanceFetcher.Factory {
|
||||
override fun create(mode: Mode, scope: CoroutineScope): DefaultAccountsBalanceFetcher
|
||||
interface Factory : PortfolioFetcher.Factory {
|
||||
override fun create(mode: Mode, scope: CoroutineScope): DefaultPortfolioFetcher
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
package com.tangem.features.account.selector
|
||||
|
||||
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
|
||||
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.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.utils.getOrElse
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.features.account.AccountSelectorComponent
|
||||
import com.tangem.features.account.AccountsBalanceFetcher
|
||||
import com.tangem.features.account.selector.entity.AccountSelectorItemUM
|
||||
import com.tangem.features.account.selector.entity.AccountSelectorUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class AccountSelectorModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<AccountSelectorComponent.Params>()
|
||||
private val balanceFetcher get() = params.accountsBalanceFetcher
|
||||
private val selectorController get() = params.controller
|
||||
|
||||
internal val state: StateFlow<AccountSelectorUM>
|
||||
field = MutableStateFlow<AccountSelectorUM>(emptyState())
|
||||
|
||||
init {
|
||||
balanceFetcher.data
|
||||
.map { data ->
|
||||
AccountSelectorUM(
|
||||
isSingleWallet = balanceFetcher.mode.value is AccountsBalanceFetcher.Mode.Wallet,
|
||||
items = buildUiList(data).toImmutableList(),
|
||||
)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun buildUiList(data: AccountsBalanceFetcher.Data) = buildList {
|
||||
fun Account.accountItemState(balance: Lce<TokenListError, TotalFiatBalance>): TokenItemState {
|
||||
val totalFiatBalance = balance.getOrElse(
|
||||
ifError = { TotalFiatBalance.Failed },
|
||||
ifLoading = { TotalFiatBalance.Loading },
|
||||
)
|
||||
return when (this) {
|
||||
is Account.CryptoPortfolio -> AccountCryptoPortfolioItemStateConverter(
|
||||
appCurrency = data.appCurrency,
|
||||
account = this,
|
||||
onItemClick = { selectorController.selectAccount(it) },
|
||||
).convert(totalFiatBalance)
|
||||
}
|
||||
}
|
||||
|
||||
data.balances.forEach { wallet, accounts ->
|
||||
if (wallet.isLocked) return@forEach
|
||||
AccountSelectorItemUM.Wallet(
|
||||
id = wallet.walletId.stringValue,
|
||||
name = stringReference(wallet.name),
|
||||
).let(::add)
|
||||
|
||||
accounts.forEach { account, balance ->
|
||||
AccountSelectorItemUM.Account(
|
||||
account = account.accountItemState(balance.balance),
|
||||
isBalanceHidden = data.isBalanceHidden,
|
||||
).let(::add)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun emptyState() = AccountSelectorUM(
|
||||
items = persistentListOf(),
|
||||
balanceFetcher.mode.value is AccountsBalanceFetcher.Mode.Wallet,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
package com.tangem.features.account.selector
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.account.AccountSelectorComponent
|
||||
import com.tangem.features.account.impl.R
|
||||
import com.tangem.features.account.selector.ui.AccountSelectorContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultAccountSelectorComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: AccountSelectorComponent.Params,
|
||||
) : AppComponentContext by appComponentContext, AccountSelectorComponent {
|
||||
|
||||
private val model: AccountSelectorModel = getOrCreateModel(params)
|
||||
|
||||
override fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
onBack = ::dismiss,
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
title = {
|
||||
TangemModalBottomSheetTitle(
|
||||
title = resourceReference(R.string.common_choose_wallet),
|
||||
startIconRes = R.drawable.ic_back_24,
|
||||
onStartClick = ::dismiss,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
AccountSelectorContent(
|
||||
state = state,
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : AccountSelectorComponent.Factory {
|
||||
override fun create(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: AccountSelectorComponent.Params,
|
||||
): DefaultAccountSelectorComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.features.account.selector
|
||||
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.features.account.AccountSelectorController
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultAccountSelectorController @Inject constructor() : AccountSelectorController {
|
||||
|
||||
private val _selectedAccount: MutableStateFlow<Account?> = MutableStateFlow(null)
|
||||
override val selectedAccount: StateFlow<Account?> get() = _selectedAccount
|
||||
|
||||
override fun selectAccount(account: Account?) {
|
||||
_selectedAccount.update { account }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.features.account.selector
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.account.PortfolioSelectorComponent
|
||||
import com.tangem.features.account.selector.ui.PortfolioSelectorBS
|
||||
import com.tangem.features.account.selector.ui.PortfolioSelectorContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
internal class DefaultPortfolioSelectorComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: PortfolioSelectorComponent.Params,
|
||||
) : AppComponentContext by appComponentContext, PortfolioSelectorComponent {
|
||||
|
||||
private val model: PortfolioSelectorModel = getOrCreateModel(params)
|
||||
|
||||
override val title: StateFlow<TextReference>
|
||||
get() = model.state
|
||||
.map { it.title }
|
||||
.stateIn(componentScope, SharingStarted.Lazily, model.state.value.title)
|
||||
|
||||
override fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
PortfolioSelectorBS(state, onDismiss = ::dismiss)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
PortfolioSelectorContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : PortfolioSelectorComponent.Factory {
|
||||
override fun create(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: PortfolioSelectorComponent.Params,
|
||||
): DefaultPortfolioSelectorComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.features.account.selector
|
||||
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.features.account.PortfolioSelectorController
|
||||
import com.tangem.features.account.PortfolioFetcher
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultPortfolioSelectorController @Inject constructor(
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
) : PortfolioSelectorController {
|
||||
|
||||
private val _selectedAccount: MutableStateFlow<AccountId?> = MutableStateFlow(null)
|
||||
|
||||
override val isAccountMode: Flow<Boolean> by lazy { isAccountsModeEnabledUseCase() }
|
||||
|
||||
override val selectedAccount: StateFlow<AccountId?> get() = _selectedAccount
|
||||
|
||||
override fun selectAccount(accountId: AccountId?) {
|
||||
_selectedAccount.update { accountId }
|
||||
}
|
||||
|
||||
override fun selectedAccountWithData(portfolioFetcher: PortfolioFetcher): Flow<Pair<UserWallet, Account>?> =
|
||||
combine(
|
||||
flow = _selectedAccount,
|
||||
flow2 = portfolioFetcher.data,
|
||||
transform = { accountId, data ->
|
||||
accountId ?: return@combine null
|
||||
var result: Pair<UserWallet, Account>? = null
|
||||
|
||||
data.balances.forEach { wallet, balance ->
|
||||
val accountStatuses = balance.accountsBalance.accountStatuses
|
||||
.find { accountId == it.account.accountId }
|
||||
if (accountStatuses != null) result = wallet to accountStatuses.account
|
||||
}
|
||||
|
||||
return@combine result
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
package com.tangem.features.account.selector
|
||||
|
||||
import com.tangem.common.ui.account.AccountPortfolioItemUMConverter
|
||||
import com.tangem.common.ui.userwallet.converter.UserWalletItemUMConverter
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
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.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.account.PortfolioFetcher
|
||||
import com.tangem.features.account.PortfolioSelectorComponent
|
||||
import com.tangem.features.account.impl.R
|
||||
import com.tangem.features.account.selector.entity.PortfolioSelectorItemUM
|
||||
import com.tangem.features.account.selector.entity.PortfolioSelectorUM
|
||||
import com.tangem.features.wallet.utils.UserWalletImageFetcher
|
||||
import com.tangem.operations.attestation.ArtworkSize
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class PortfolioSelectorModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val walletImageFetcher: UserWalletImageFetcher,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<PortfolioSelectorComponent.Params>()
|
||||
private val balanceFetcher get() = params.portfolioFetcher
|
||||
private val selectorController get() = params.controller
|
||||
|
||||
internal val state: StateFlow<PortfolioSelectorUM>
|
||||
field = MutableStateFlow<PortfolioSelectorUM>(emptyState())
|
||||
|
||||
init {
|
||||
combine(
|
||||
flow = isAccountsModeEnabledUseCase(),
|
||||
flow2 = loadBalanceWithArtwork(),
|
||||
transform = { isAccountsMode, (portfolioData, artworks) ->
|
||||
val uiList = buildUiList(isAccountsMode, portfolioData, artworks)
|
||||
val title = when (isAccountsMode) {
|
||||
true -> resourceReference(R.string.common_choose_account)
|
||||
false -> resourceReference(R.string.common_choose_wallet)
|
||||
}
|
||||
state.value = PortfolioSelectorUM(
|
||||
title = title,
|
||||
items = uiList.toImmutableList(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun buildUiList(
|
||||
isAccountsMode: Boolean,
|
||||
portfolioData: PortfolioFetcher.Data,
|
||||
artworks: Map<UserWalletId, UserWalletItemUM.ImageState>,
|
||||
): List<PortfolioSelectorItemUM> = when (isAccountsMode) {
|
||||
true -> buildAccountsList(portfolioData, artworks)
|
||||
false -> buildWalletList(portfolioData, artworks)
|
||||
}
|
||||
|
||||
private fun buildWalletList(
|
||||
portfolioData: PortfolioFetcher.Data,
|
||||
artworks: Map<UserWalletId, UserWalletItemUM.ImageState>,
|
||||
): List<PortfolioSelectorItemUM> = buildList {
|
||||
val appCurrency = portfolioData.appCurrency
|
||||
val isBalanceHidden = portfolioData.isBalanceHidden
|
||||
val lockedWallets = mutableListOf<PortfolioSelectorItemUM>()
|
||||
portfolioData.balances.forEach { wallet, portfolio ->
|
||||
val balance = portfolio.walletBalance.getOrNull()
|
||||
val walletItemUM = UserWalletItemUMConverter(
|
||||
onClick = {
|
||||
// todo account
|
||||
// selectorController.selectAccount(portfolio.accountsBalance.mainAccount)
|
||||
},
|
||||
appCurrency = appCurrency,
|
||||
balance = balance,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
artwork = artworks[wallet.walletId],
|
||||
isAuthMode = false,
|
||||
).convert(wallet)
|
||||
if (walletItemUM.isEnabled) {
|
||||
add(PortfolioSelectorItemUM.Portfolio(walletItemUM))
|
||||
} else {
|
||||
lockedWallets.add(PortfolioSelectorItemUM.Portfolio(walletItemUM))
|
||||
}
|
||||
}
|
||||
if (lockedWallets.isNotEmpty()) {
|
||||
val lockedWalletsTitle = PortfolioSelectorItemUM.GroupTitle(
|
||||
id = "lockedWalletsTitleId",
|
||||
name = resourceReference(R.string.common_locked_wallets),
|
||||
)
|
||||
add(lockedWalletsTitle)
|
||||
addAll(lockedWallets)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildAccountsList(
|
||||
portfolioData: PortfolioFetcher.Data,
|
||||
artworks: Map<UserWalletId, UserWalletItemUM.ImageState>,
|
||||
): List<PortfolioSelectorItemUM> = buildList {
|
||||
val appCurrency = portfolioData.appCurrency
|
||||
val isBalanceHidden = portfolioData.isBalanceHidden
|
||||
val lockedWallets = mutableListOf<PortfolioSelectorItemUM>()
|
||||
portfolioData.balances.forEach { wallet, portfolio ->
|
||||
val balance = portfolio.walletBalance.getOrNull()
|
||||
val walletItemUM = UserWalletItemUMConverter(
|
||||
onClick = {
|
||||
// todo account
|
||||
// selectorController.selectAccount(portfolio.accountsBalance.mainAccount)
|
||||
},
|
||||
appCurrency = appCurrency,
|
||||
balance = balance,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
artwork = artworks[wallet.walletId],
|
||||
isAuthMode = false,
|
||||
).convert(wallet)
|
||||
if (!walletItemUM.isEnabled) {
|
||||
lockedWallets.add(PortfolioSelectorItemUM.Portfolio(walletItemUM))
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val walletTitle = PortfolioSelectorItemUM.GroupTitle(
|
||||
id = "GroupTitle ${wallet.walletId.stringValue}",
|
||||
name = stringReference(wallet.name),
|
||||
)
|
||||
add(walletTitle)
|
||||
|
||||
add(PortfolioSelectorItemUM.Portfolio(walletItemUM))
|
||||
portfolio.accountsBalance.accountStatuses.forEach { accountStatus ->
|
||||
val account = accountStatus.account
|
||||
val accountBalance = when (accountStatus) {
|
||||
is AccountStatus.CryptoPortfolio -> accountStatus.tokenList.totalFiatBalance
|
||||
}
|
||||
val accountItemUM = AccountPortfolioItemUMConverter(
|
||||
onClick = { selectorController.selectAccount(account.accountId) },
|
||||
appCurrency = appCurrency,
|
||||
accountBalance = accountBalance,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
).convert(account)
|
||||
add(PortfolioSelectorItemUM.Portfolio(accountItemUM))
|
||||
}
|
||||
}
|
||||
if (lockedWallets.isNotEmpty()) {
|
||||
val lockedWalletsTitle = PortfolioSelectorItemUM.GroupTitle(
|
||||
id = "lockedWalletsTitleId",
|
||||
name = resourceReference(R.string.common_locked_wallets),
|
||||
)
|
||||
add(lockedWalletsTitle)
|
||||
addAll(lockedWallets)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadBalanceWithArtwork():
|
||||
Flow<Pair<PortfolioFetcher.Data, Map<UserWalletId, UserWalletItemUM.ImageState>>> {
|
||||
val wallets = Channel<Set<UserWallet>>()
|
||||
val portfolioFlow = balanceFetcher.data
|
||||
.onEach { wallets.trySend(it.balances.keys) }
|
||||
|
||||
val artworksFlow = wallets.receiveAsFlow()
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { walletImageFetcher.walletsImage(wallets = it, size = ArtworkSize.SMALL) }
|
||||
|
||||
return combine(
|
||||
flow = portfolioFlow,
|
||||
flow2 = artworksFlow,
|
||||
) { portfolioData, artworks -> portfolioData to artworks }
|
||||
}
|
||||
|
||||
private fun emptyState() = PortfolioSelectorUM(
|
||||
items = persistentListOf(),
|
||||
title = TextReference.EMPTY,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package com.tangem.features.account.selector.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class AccountSelectorUM(
|
||||
val items: ImmutableList<AccountSelectorItemUM>,
|
||||
val isSingleWallet: Boolean,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
sealed interface AccountSelectorItemUM {
|
||||
val id: String
|
||||
|
||||
data class Wallet(
|
||||
override val id: String,
|
||||
val name: TextReference,
|
||||
) : AccountSelectorItemUM
|
||||
|
||||
data class Account(
|
||||
val account: TokenItemState,
|
||||
val isBalanceHidden: Boolean,
|
||||
) : AccountSelectorItemUM {
|
||||
override val id: String = account.id
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.features.account.selector.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class PortfolioSelectorUM(
|
||||
val title: TextReference,
|
||||
val items: ImmutableList<PortfolioSelectorItemUM>,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
sealed interface PortfolioSelectorItemUM {
|
||||
val id: String
|
||||
|
||||
data class GroupTitle(
|
||||
override val id: String,
|
||||
val name: TextReference,
|
||||
) : PortfolioSelectorItemUM
|
||||
|
||||
data class Portfolio(
|
||||
val item: UserWalletItemUM,
|
||||
) : PortfolioSelectorItemUM {
|
||||
override val id: String = item.id.stringValue
|
||||
}
|
||||
}
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
package com.tangem.features.account.selector.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.token.AccountItemPreviewData
|
||||
import com.tangem.core.ui.components.token.TokenItem
|
||||
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.TangemThemePreview
|
||||
import com.tangem.features.account.selector.entity.AccountSelectorItemUM
|
||||
import com.tangem.features.account.selector.entity.AccountSelectorUM
|
||||
import com.tangem.features.account.selector.ui.AccountSelectorPreviewData.firstList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.util.UUID
|
||||
|
||||
@Composable
|
||||
internal fun AccountSelectorContent(
|
||||
state: AccountSelectorUM,
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = PaddingValues(),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
val items = state.items
|
||||
itemsIndexed(
|
||||
items = items,
|
||||
key = { _, item -> item.id },
|
||||
) { index, item ->
|
||||
val previewItem = items.getOrNull(index.dec())
|
||||
val offsetModifier = when {
|
||||
previewItem == null -> Modifier
|
||||
state.isSingleWallet -> Modifier.padding(
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
)
|
||||
item is AccountSelectorItemUM.Wallet -> Modifier.padding(
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
)
|
||||
else -> Modifier.padding(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
)
|
||||
}
|
||||
|
||||
when (item) {
|
||||
is AccountSelectorItemUM.Account -> TokenItem(
|
||||
state = item.account,
|
||||
isBalanceHidden = item.isBalanceHidden,
|
||||
modifier = offsetModifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius14))
|
||||
.background(color = TangemTheme.colors.background.primary),
|
||||
)
|
||||
is AccountSelectorItemUM.Wallet -> WalletNameRow(
|
||||
model = item,
|
||||
modifier = offsetModifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletNameRow(model: AccountSelectorItemUM.Wallet, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = model.name.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AccountSelectorContentPreview(
|
||||
@PreviewParameter(AccountSelectorPreviewStateProvider::class) params: AccountSelectorUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
AccountSelectorContent(
|
||||
state = params,
|
||||
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal object AccountSelectorPreviewData {
|
||||
val firstList
|
||||
get() = buildList {
|
||||
AccountSelectorItemUM.Wallet(
|
||||
id = UUID.randomUUID().toString(),
|
||||
name = stringReference("Tangem 2.0"),
|
||||
).let(::add)
|
||||
AccountItemPreviewData.accountItem
|
||||
.let { AccountSelectorItemUM.Account(it, false) }
|
||||
.let(::add)
|
||||
AccountItemPreviewData.accountItem.copy(iconState = AccountItemPreviewData.accountLetterIcon)
|
||||
.let { AccountSelectorItemUM.Account(it, false) }
|
||||
.let(::add)
|
||||
AccountSelectorItemUM.Wallet(
|
||||
id = UUID.randomUUID().toString(),
|
||||
name = stringReference("Tangem White"),
|
||||
).let(::add)
|
||||
AccountItemPreviewData.accountItem.copy(iconState = AccountItemPreviewData.accountLetterIcon)
|
||||
.let { AccountSelectorItemUM.Account(it, false) }
|
||||
.let(::add)
|
||||
}
|
||||
}
|
||||
|
||||
internal class AccountSelectorPreviewStateProvider : CollectionPreviewParameterProvider<AccountSelectorUM>(
|
||||
buildList {
|
||||
val secondList = listOf(
|
||||
AccountItemPreviewData.accountItem,
|
||||
AccountItemPreviewData.accountItem.copy(iconState = AccountItemPreviewData.accountLetterIcon),
|
||||
).map { AccountSelectorItemUM.Account(it, false) }
|
||||
|
||||
val first = AccountSelectorUM(
|
||||
items = firstList.toImmutableList(),
|
||||
isSingleWallet = false,
|
||||
)
|
||||
val second = AccountSelectorUM(
|
||||
items = secondList.toImmutableList(),
|
||||
isSingleWallet = true,
|
||||
)
|
||||
add(first)
|
||||
add(second)
|
||||
},
|
||||
)
|
||||
|
|
@ -13,14 +13,13 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.account.impl.R
|
||||
import com.tangem.features.account.selector.entity.AccountSelectorUM
|
||||
import com.tangem.features.account.selector.entity.PortfolioSelectorUM
|
||||
|
||||
@Composable
|
||||
internal fun AccountSelectorBS(state: AccountSelectorUM, onDismiss: () -> Unit, modifier: Modifier = Modifier) {
|
||||
internal fun PortfolioSelectorBS(state: PortfolioSelectorUM, onDismiss: () -> Unit, modifier: Modifier = Modifier) {
|
||||
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
|
|
@ -32,13 +31,13 @@ internal fun AccountSelectorBS(state: AccountSelectorUM, onDismiss: () -> Unit,
|
|||
containerColor = TangemTheme.colors.background.secondary,
|
||||
title = {
|
||||
TangemModalBottomSheetTitle(
|
||||
title = resourceReference(R.string.common_choose_account),
|
||||
title = state.title,
|
||||
startIconRes = R.drawable.ic_back_24,
|
||||
onStartClick = onDismiss,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
AccountSelectorContent(
|
||||
PortfolioSelectorContent(
|
||||
state = state,
|
||||
contentPadding = PaddingValues(bottom = 16.dp),
|
||||
modifier = modifier.padding(horizontal = 16.dp),
|
||||
|
|
@ -50,9 +49,9 @@ internal fun AccountSelectorBS(state: AccountSelectorUM, onDismiss: () -> Unit,
|
|||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview(@PreviewParameter(AccountSelectorPreviewStateProvider::class) params: AccountSelectorUM) {
|
||||
private fun Preview(@PreviewParameter(PortfolioSelectorPreviewStateProvider::class) params: PortfolioSelectorUM) {
|
||||
TangemThemePreview {
|
||||
AccountSelectorBS(
|
||||
PortfolioSelectorBS(
|
||||
state = params,
|
||||
onDismiss = {},
|
||||
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
package com.tangem.features.account.selector.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.common.ui.account.AccountIconPreviewData
|
||||
import com.tangem.common.ui.userwallet.UserWalletItem
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM.ImageState
|
||||
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.TangemThemePreview
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.account.impl.R
|
||||
import com.tangem.features.account.selector.entity.PortfolioSelectorItemUM
|
||||
import com.tangem.features.account.selector.entity.PortfolioSelectorUM
|
||||
import com.tangem.features.account.selector.ui.PortfolioSelectorPreviewData.firstList
|
||||
import com.tangem.features.account.selector.ui.PortfolioSelectorPreviewData.lockedWalletList
|
||||
import com.tangem.features.account.selector.ui.PortfolioSelectorPreviewData.secondList
|
||||
import com.tangem.features.account.selector.ui.PortfolioSelectorPreviewData.walletList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.util.UUID
|
||||
|
||||
private const val DISABLED_WALLET_ALPHA = 0.5f
|
||||
|
||||
@Composable
|
||||
internal fun PortfolioSelectorContent(
|
||||
state: PortfolioSelectorUM,
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = PaddingValues(),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
val items = state.items
|
||||
itemsIndexed(
|
||||
items = items,
|
||||
key = { _, item -> item.id },
|
||||
) { index, item ->
|
||||
val previewItem = items.getOrNull(index.dec())
|
||||
val offsetModifier = when {
|
||||
previewItem == null -> Modifier
|
||||
item is PortfolioSelectorItemUM.GroupTitle -> Modifier.padding(
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
)
|
||||
else -> Modifier.padding(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
)
|
||||
}
|
||||
|
||||
when (item) {
|
||||
is PortfolioSelectorItemUM.Portfolio -> UserWalletItem(
|
||||
state = item.item,
|
||||
modifier = offsetModifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius14))
|
||||
.background(color = TangemTheme.colors.background.primary)
|
||||
.let { if (!item.item.isEnabled) it.alpha(DISABLED_WALLET_ALPHA) else it },
|
||||
)
|
||||
is PortfolioSelectorItemUM.GroupTitle -> WalletNameRow(
|
||||
model = item,
|
||||
modifier = offsetModifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletNameRow(model: PortfolioSelectorItemUM.GroupTitle, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = model.name.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview(@PreviewParameter(PortfolioSelectorPreviewStateProvider::class) params: PortfolioSelectorUM) {
|
||||
TangemThemePreview {
|
||||
PortfolioSelectorContent(
|
||||
state = params,
|
||||
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal object PortfolioSelectorPreviewData {
|
||||
|
||||
val accountName get() = stringReference(value = "Portfolio")
|
||||
val walletName get() = stringReference(value = "Tangem 2.0")
|
||||
|
||||
private val accountItem: UserWalletItemUM
|
||||
get() = UserWalletItemUM(
|
||||
id = UserWalletId(UUID.randomUUID().toString().encodeToByteArray()),
|
||||
name = accountName,
|
||||
information = UserWalletItemUM.Information.Loaded(stringReference("12 tokens")),
|
||||
balance = UserWalletItemUM.Balance.Loaded("$726.04", false),
|
||||
isEnabled = true,
|
||||
onClick = { },
|
||||
imageState = ImageState.Account(
|
||||
name = accountName,
|
||||
icon = AccountIconPreviewData.randomAccountIcon(),
|
||||
),
|
||||
label = null,
|
||||
)
|
||||
|
||||
private val walletItem: UserWalletItemUM
|
||||
get() = UserWalletItemUM(
|
||||
id = UserWalletId(UUID.randomUUID().toString().encodeToByteArray()),
|
||||
name = walletName,
|
||||
information = UserWalletItemUM.Information.Loaded(stringReference("12 tokens")),
|
||||
balance = UserWalletItemUM.Balance.Loaded("$726.04", false),
|
||||
isEnabled = true,
|
||||
onClick = { },
|
||||
imageState = ImageState.MobileWallet,
|
||||
label = null,
|
||||
)
|
||||
|
||||
private val lockedWalletItem: UserWalletItemUM
|
||||
get() = walletItem.copy(isEnabled = false)
|
||||
|
||||
val firstList
|
||||
get() = buildList {
|
||||
PortfolioSelectorItemUM.GroupTitle(
|
||||
id = UUID.randomUUID().toString(),
|
||||
name = stringReference("Tangem 2.0"),
|
||||
).let(::add)
|
||||
accountItem
|
||||
.let { PortfolioSelectorItemUM.Portfolio(it) }
|
||||
.let(::add)
|
||||
accountItem
|
||||
.let { PortfolioSelectorItemUM.Portfolio(it) }
|
||||
.let(::add)
|
||||
PortfolioSelectorItemUM.GroupTitle(
|
||||
id = UUID.randomUUID().toString(),
|
||||
name = stringReference("Tangem White"),
|
||||
).let(::add)
|
||||
accountItem.let { PortfolioSelectorItemUM.Portfolio(it) }
|
||||
.let(::add)
|
||||
}
|
||||
|
||||
val secondList
|
||||
get() = firstList + buildList {
|
||||
PortfolioSelectorItemUM.GroupTitle(
|
||||
id = UUID.randomUUID().toString(),
|
||||
name = resourceReference(R.string.common_locked_wallets),
|
||||
).let(::add)
|
||||
add(PortfolioSelectorItemUM.Portfolio(lockedWalletItem))
|
||||
add(PortfolioSelectorItemUM.Portfolio(lockedWalletItem))
|
||||
}
|
||||
|
||||
val walletList
|
||||
get() = buildList {
|
||||
add(PortfolioSelectorItemUM.Portfolio(walletItem))
|
||||
add(PortfolioSelectorItemUM.Portfolio(walletItem))
|
||||
}
|
||||
|
||||
val lockedWalletList
|
||||
get() = buildList {
|
||||
add(PortfolioSelectorItemUM.Portfolio(walletItem))
|
||||
val title = PortfolioSelectorItemUM.GroupTitle(
|
||||
id = UUID.randomUUID().toString(),
|
||||
name = resourceReference(R.string.common_locked_wallets),
|
||||
)
|
||||
add(title)
|
||||
add(PortfolioSelectorItemUM.Portfolio(lockedWalletItem))
|
||||
add(PortfolioSelectorItemUM.Portfolio(lockedWalletItem))
|
||||
}
|
||||
}
|
||||
|
||||
internal class PortfolioSelectorPreviewStateProvider : CollectionPreviewParameterProvider<PortfolioSelectorUM>(
|
||||
buildList {
|
||||
val first = PortfolioSelectorUM(
|
||||
title = resourceReference(R.string.common_choose_account),
|
||||
items = firstList.toImmutableList(),
|
||||
)
|
||||
val second = PortfolioSelectorUM(
|
||||
title = resourceReference(R.string.common_choose_account),
|
||||
items = secondList.toImmutableList(),
|
||||
)
|
||||
val walletListUM = PortfolioSelectorUM(
|
||||
title = resourceReference(R.string.common_choose_wallet),
|
||||
items = walletList.toImmutableList(),
|
||||
)
|
||||
val lockedWalletListUM =
|
||||
PortfolioSelectorUM(
|
||||
title = resourceReference(R.string.common_choose_wallet),
|
||||
items = lockedWalletList.toImmutableList(),
|
||||
)
|
||||
add(first)
|
||||
add(second)
|
||||
add(walletListUM)
|
||||
add(lockedWalletListUM)
|
||||
},
|
||||
)
|
||||
|
|
@ -13,7 +13,7 @@ import com.tangem.core.ui.message.DialogMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.settings.SetAskBiometryShownUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
|||
import com.tangem.domain.card.analytics.Shop
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
|
|
@ -61,14 +61,14 @@ internal class CreateWalletSelectionModel @Inject constructor(
|
|||
) : Model() {
|
||||
|
||||
internal val uiState: StateFlow<CreateWalletSelectionUM>
|
||||
field = MutableStateFlow(
|
||||
CreateWalletSelectionUM(
|
||||
onBackClick = { router.pop() },
|
||||
onMobileWalletClick = ::onMobileWalletClick,
|
||||
onHardwareWalletClick = ::onHardwareWalletClick,
|
||||
onScanClick = ::onScanClick,
|
||||
),
|
||||
)
|
||||
field = MutableStateFlow(
|
||||
CreateWalletSelectionUM(
|
||||
onBackClick = { router.pop() },
|
||||
onMobileWalletClick = ::onMobileWalletClick,
|
||||
onHardwareWalletClick = ::onHardwareWalletClick,
|
||||
onScanClick = ::onScanClick,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
showAlreadyHaveWalletWithDelay()
|
||||
|
|
|
|||
|
|
@ -140,11 +140,11 @@ internal fun CreateWalletSelectionContent(state: CreateWalletSelectionUM, modifi
|
|||
|
||||
@Composable
|
||||
private fun WalletBlock(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
description: String,
|
||||
badge: @Composable () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
badge: @Composable () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.common.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase
|
||||
import com.tangem.features.details.impl.R
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
|||
import com.tangem.domain.card.analytics.Shop
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
|
@ -46,14 +46,7 @@ import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
|
|
|
|||
|
|
@ -95,11 +95,11 @@ fun StoriesTextAnimation(
|
|||
|
||||
@Composable
|
||||
fun StoriesBottomImageAnimation(
|
||||
firstStepDuration: Int,
|
||||
totalDuration: Int,
|
||||
initialScale: Float = 2.5f,
|
||||
secondStageScale: Float = SCALE_SWITCH_BARRIER,
|
||||
targetScale: Float = 1.0f,
|
||||
firstStepDuration: Int,
|
||||
totalDuration: Int,
|
||||
content: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
val secondStepDuration = totalDuration - firstStepDuration
|
||||
|
|
|
|||
|
|
@ -25,10 +25,7 @@ internal class AccessCodeComponent @AssistedInject constructor(
|
|||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
if (!state.isConfirmMode) {
|
||||
DisableScreenshotsDisposableEffect()
|
||||
}
|
||||
|
||||
DisableScreenshotsDisposableEffect()
|
||||
AccessCode(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ 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.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.components.fields.PinTextColor
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
|
|
@ -56,10 +57,11 @@ internal class AccessCodeModel @Inject constructor(
|
|||
private val params = paramsContainer.require<AccessCodeComponent.Params>()
|
||||
|
||||
internal val uiState: StateFlow<AccessCodeUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
private fun getInitialState() = AccessCodeUM(
|
||||
accessCode = "",
|
||||
accessCodeColor = PinTextColor.Primary,
|
||||
onAccessCodeChange = ::onAccessCodeChange,
|
||||
isConfirmMode = params.accessCodeToConfirm != null,
|
||||
buttonEnabled = false,
|
||||
|
|
@ -76,6 +78,12 @@ internal class AccessCodeModel @Inject constructor(
|
|||
} else {
|
||||
value.length == uiState.value.accessCodeLength
|
||||
},
|
||||
accessCodeColor = when {
|
||||
params.accessCodeToConfirm == null -> PinTextColor.Primary
|
||||
value.length != uiState.value.accessCodeLength -> PinTextColor.Primary
|
||||
value == params.accessCodeToConfirm -> PinTextColor.Success
|
||||
else -> PinTextColor.WrongCode
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.features.hotwallet.accesscode.entity
|
||||
|
||||
import com.tangem.core.ui.components.fields.PinTextColor
|
||||
import com.tangem.features.hotwallet.accesscode.ACCESS_CODE_LENGTH
|
||||
|
||||
internal data class AccessCodeUM(
|
||||
val accessCode: String,
|
||||
val accessCodeColor: PinTextColor,
|
||||
val onAccessCodeChange: (String) -> Unit,
|
||||
val isConfirmMode: Boolean,
|
||||
val buttonEnabled: Boolean,
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ internal fun AccessCode(state: AccessCodeUM, modifier: Modifier = Modifier) {
|
|||
length = state.accessCodeLength,
|
||||
isPasswordVisual = state.isConfirmMode,
|
||||
value = state.accessCode,
|
||||
pinTextColor = PinTextColor.Primary,
|
||||
pinTextColor = state.accessCodeColor,
|
||||
onValueChange = state.onAccessCodeChange,
|
||||
)
|
||||
}
|
||||
|
|
@ -109,6 +109,7 @@ private fun PreviewSet() {
|
|||
AccessCode(
|
||||
state = AccessCodeUM(
|
||||
accessCode = "",
|
||||
accessCodeColor = PinTextColor.Primary,
|
||||
onAccessCodeChange = {},
|
||||
isConfirmMode = false,
|
||||
buttonEnabled = false,
|
||||
|
|
@ -127,6 +128,7 @@ private fun PreviewConfirm() {
|
|||
AccessCode(
|
||||
state = AccessCodeUM(
|
||||
accessCode = "123456",
|
||||
accessCodeColor = PinTextColor.Success,
|
||||
onAccessCodeChange = {},
|
||||
isConfirmMode = true,
|
||||
buttonEnabled = true,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.FullScreen
|
||||
import com.tangem.core.ui.components.DialogFullScreen
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.features.hotwallet.HotAccessCodeRequestComponent
|
||||
import com.tangem.features.hotwallet.accesscoderequest.ui.HotAccessCodeRequestFullScreenContent
|
||||
|
|
@ -47,7 +47,7 @@ internal class DefaultHotAccessCodeRequestComponent @AssistedInject constructor(
|
|||
var isShownIfProxy by remember { mutableStateOf(state.isShown) }
|
||||
|
||||
if (isShownIfProxy) {
|
||||
FullScreen(focusable = true, onBackClick = state.onDismiss) {
|
||||
DialogFullScreen(onDismissRequest = state.onDismiss) {
|
||||
HotAccessCodeRequestFullScreenContent(
|
||||
state = state.copy(isShown = isShownProxy),
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ import com.tangem.core.ui.components.fields.PinTextColor
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository.Attempts
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository.Companion.MAX_FAST_FORWARD_ATTEMPTS
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.features.hotwallet.accesscode.ACCESS_CODE_LENGTH
|
||||
import com.tangem.features.hotwallet.accesscoderequest.entity.HotAccessCodeRequestUM
|
||||
|
|
@ -43,7 +44,7 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
)
|
||||
|
||||
val uiState: StateFlow<HotAccessCodeRequestUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
suspend fun show(attemptRequest: HotWalletPasswordRequester.AttemptRequest) {
|
||||
if (userWalletExists(attemptRequest.hotWalletId).not()) {
|
||||
|
|
@ -82,6 +83,7 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
it.copy(
|
||||
accessCodeColor = PinTextColor.WrongCode,
|
||||
onAccessCodeChange = {},
|
||||
useBiometricVisible = currentRequest.hasBiometry,
|
||||
)
|
||||
}
|
||||
delay(timeMillis = 500) // Delay to show the wrong access code state
|
||||
|
|
@ -121,7 +123,10 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
|
||||
if (accessCode.length == ACCESS_CODE_LENGTH) {
|
||||
uiState.update {
|
||||
it.copy(onAccessCodeChange = {})
|
||||
it.copy(
|
||||
onAccessCodeChange = {},
|
||||
useBiometricVisible = false,
|
||||
)
|
||||
}
|
||||
|
||||
result.value = HotWalletPasswordRequester.Result.EnteredPassword(HotAuth.Password(accessCode.toCharArray()))
|
||||
|
|
@ -143,7 +148,24 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
suspend fun collectAttempts(attempts: Attempts) {
|
||||
when (attempts) {
|
||||
is Attempts.FastForward -> {
|
||||
/** ignore */
|
||||
if (attempts.count > 0) {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
wrongAccessCodeText = resourceReference(
|
||||
R.string.access_code_check_warining_lock,
|
||||
wrappedList(MAX_FAST_FORWARD_ATTEMPTS - attempts.count),
|
||||
),
|
||||
onAccessCodeChange = ::onAccessCodeChange,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
wrongAccessCodeText = null,
|
||||
onAccessCodeChange = ::onAccessCodeChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is Attempts.WithDelay -> {
|
||||
uiState.update {
|
||||
|
|
|
|||
|
|
@ -37,105 +37,110 @@ import com.tangem.features.hotwallet.impl.R
|
|||
@Suppress("MagicNumber", "LongMethod")
|
||||
@Composable
|
||||
internal fun HotAccessCodeRequestFullScreenContent(state: HotAccessCodeRequestUM, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier,
|
||||
visible = state.isShown,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier,
|
||||
visible = state.isShown,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding(),
|
||||
startButton = TopAppBarButtonUM.Back(state.onDismiss),
|
||||
)
|
||||
|
||||
SpacerH(68.dp)
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp),
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.animateEnterExit(
|
||||
enter = slideInVertically(
|
||||
tween(),
|
||||
initialOffsetY = { it + 200 },
|
||||
) + fadeIn(tween()),
|
||||
exit = slideOutVertically(tween(300)) { it - 200 } + fadeOut(tween()),
|
||||
),
|
||||
text = stringResourceSafe(R.string.access_code_check_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
startButton = TopAppBarButtonUM.Back(state.onDismiss),
|
||||
)
|
||||
|
||||
SpacerH24()
|
||||
SpacerH(68.dp)
|
||||
|
||||
PinTextField(
|
||||
modifier = Modifier.animateEnterExit(
|
||||
enter = slideInVertically(
|
||||
tween(),
|
||||
initialOffsetY = { it + 200 },
|
||||
) + fadeIn(tween()),
|
||||
exit = slideOutVertically(tween(300)) { it - 200 } + fadeOut(tween()),
|
||||
),
|
||||
length = 6,
|
||||
isPasswordVisual = true,
|
||||
value = state.accessCode,
|
||||
pinTextColor = state.accessCodeColor,
|
||||
onValueChange = state.onAccessCodeChange,
|
||||
)
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.animateEnterExit(
|
||||
enter = slideInVertically(
|
||||
tween(),
|
||||
initialOffsetY = { it + 200 },
|
||||
) + fadeIn(tween()),
|
||||
exit = slideOutVertically(tween(300)) { it - 200 } + fadeOut(tween()),
|
||||
),
|
||||
text = stringResourceSafe(R.string.access_code_check_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
SpacerH(20.dp)
|
||||
SpacerH24()
|
||||
|
||||
PinTextField(
|
||||
modifier = Modifier.animateEnterExit(
|
||||
enter = slideInVertically(
|
||||
tween(),
|
||||
initialOffsetY = { it + 200 },
|
||||
) + fadeIn(tween()),
|
||||
exit = slideOutVertically(tween(300)) { it - 200 } + fadeOut(tween()),
|
||||
),
|
||||
length = 6,
|
||||
isPasswordVisual = true,
|
||||
value = state.accessCode,
|
||||
pinTextColor = state.accessCodeColor,
|
||||
onValueChange = state.onAccessCodeChange,
|
||||
)
|
||||
|
||||
SpacerH(20.dp)
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.animateEnterExit(
|
||||
enter = slideInVertically(
|
||||
tween(),
|
||||
initialOffsetY = { it + 200 },
|
||||
) + fadeIn(tween()),
|
||||
exit = slideOutVertically(tween(300)) { it - 200 } + fadeOut(tween()),
|
||||
),
|
||||
visible = state.wrongAccessCodeText != null,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
val wrongAccessCodeText =
|
||||
state.wrongAccessCodeText ?: return@AnimatedVisibility
|
||||
|
||||
Text(
|
||||
text = wrongAccessCodeText.resolveReference(),
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.caption2.copy(
|
||||
lineBreak = LineBreak.Heading,
|
||||
),
|
||||
color = TangemTheme.colors.text.warning,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.animateEnterExit(
|
||||
enter = slideInVertically(
|
||||
tween(),
|
||||
initialOffsetY = { it + 200 },
|
||||
) + fadeIn(tween()),
|
||||
exit = slideOutVertically(tween(300)) { it - 200 } + fadeOut(tween()),
|
||||
),
|
||||
visible = state.wrongAccessCodeText != null,
|
||||
visible = state.useBiometricVisible,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
val wrongAccessCodeText =
|
||||
state.wrongAccessCodeText ?: return@AnimatedVisibility
|
||||
|
||||
Text(
|
||||
text = wrongAccessCodeText.resolveReference(),
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.caption2.copy(
|
||||
lineBreak = LineBreak.Heading,
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding()
|
||||
.imePadding(),
|
||||
text = stringResourceSafe(
|
||||
id = R.string.welcome_unlock,
|
||||
stringResourceSafe(R.string.common_biometrics),
|
||||
),
|
||||
color = TangemTheme.colors.text.warning,
|
||||
onClick = state.useBiometricClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.useBiometricVisible) {
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding()
|
||||
.imePadding(),
|
||||
text = stringResourceSafe(
|
||||
id = R.string.welcome_unlock,
|
||||
stringResourceSafe(R.string.common_biometrics),
|
||||
),
|
||||
onClick = state.useBiometricClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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.ComposableContentComponent
|
||||
import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.model.AddExistingWalletImportModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.ui.AddExistingWalletImportContent
|
||||
|
|
@ -22,6 +23,7 @@ internal class AddExistingWalletImportComponent @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
DisableScreenshotsDisposableEffect()
|
||||
AddExistingWalletImportContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -6,16 +6,12 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
|
||||
import com.tangem.core.ui.components.bottomsheets.message.icon
|
||||
import com.tangem.core.ui.components.bottomsheets.message.infoBlock
|
||||
import com.tangem.core.ui.components.bottomsheets.message.onClick
|
||||
import com.tangem.core.ui.components.bottomsheets.message.secondaryButton
|
||||
import com.tangem.core.ui.components.bottomsheets.message.*
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.core.ui.message.bottomSheetMessage
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.common.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.wallets.builder.HotUserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.features.hotwallet.MnemonicRepository
|
||||
|
|
@ -80,7 +76,7 @@ internal class AddExistingWalletImportModel @Inject constructor(
|
|||
}
|
||||
|
||||
internal val uiState: StateFlow<AddExistingWalletImportUM>
|
||||
field = MutableStateFlow(importSeedPhraseUiStateBuilder.getState())
|
||||
field = MutableStateFlow(importSeedPhraseUiStateBuilder.getState())
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun importWallet(mnemonic: Mnemonic, passphrase: String?) {
|
||||
|
|
|
|||
|
|
@ -67,13 +67,17 @@ internal class ImportSeedPhraseUiStateBuilder(
|
|||
val text = st.words.text
|
||||
val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
|
||||
val newWords = wordsFromText.dropLast(1) + word
|
||||
val newWordsText = newWords.joinToString(" ")
|
||||
st.copy(
|
||||
words = TextFieldValue(
|
||||
text = newWordsText,
|
||||
selection = TextRange(newWordsText.length),
|
||||
),
|
||||
val newWordsText = newWords.joinToString(" ").plus(" ")
|
||||
val newWordsState = TextFieldValue(
|
||||
text = newWordsText,
|
||||
selection = TextRange(newWordsText.length),
|
||||
)
|
||||
st.copy(
|
||||
words = newWordsState,
|
||||
).also {
|
||||
launchInterceptWords(wordsField = newWordsState)
|
||||
suggestNextWord(newWordsState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
|||
import com.tangem.domain.card.analytics.Shop
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
|
|
@ -63,16 +63,16 @@ internal class AddExistingWalletStartModel @Inject constructor(
|
|||
private val params: AddExistingWalletStartComponent.Params = paramsContainer.require()
|
||||
|
||||
internal val uiState: StateFlow<AddExistingWalletStartUM>
|
||||
field = MutableStateFlow(
|
||||
AddExistingWalletStartUM(
|
||||
showWantToPurchaseBlock = false,
|
||||
isScanInProgress = false,
|
||||
onBackClick = params.callbacks::onBackClick,
|
||||
onImportPhraseClick = params.callbacks::onImportPhraseClick,
|
||||
onScanCardClick = ::onScanClick,
|
||||
onBuyCardClick = ::onShopClick,
|
||||
),
|
||||
)
|
||||
field = MutableStateFlow(
|
||||
AddExistingWalletStartUM(
|
||||
showWantToPurchaseBlock = false,
|
||||
isScanInProgress = false,
|
||||
onBackClick = params.callbacks::onBackClick,
|
||||
onImportPhraseClick = params.callbacks::onImportPhraseClick,
|
||||
onScanCardClick = ::onScanClick,
|
||||
onBuyCardClick = ::onShopClick,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
showWantToPurchaseBlockWithDelay()
|
||||
|
|
|
|||
|
|
@ -28,13 +28,13 @@ internal class CreateMobileWalletModel @Inject constructor(
|
|||
) : Model() {
|
||||
|
||||
internal val uiState: StateFlow<CreateMobileWalletUM>
|
||||
field = MutableStateFlow(
|
||||
CreateMobileWalletUM(
|
||||
onBackClick = { router.pop() },
|
||||
onCreateClick = ::onCreateClick,
|
||||
createButtonLoading = false,
|
||||
),
|
||||
)
|
||||
field = MutableStateFlow(
|
||||
CreateMobileWalletUM(
|
||||
onBackClick = { router.pop() },
|
||||
onCreateClick = ::onCreateClick,
|
||||
createButtonLoading = false,
|
||||
),
|
||||
)
|
||||
|
||||
private fun onCreateClick() {
|
||||
modelScope.launch {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckCompone
|
|||
import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.phrase.ManualBackupPhraseComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.start.ManualBackupStartComponent
|
||||
import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import javax.inject.Inject
|
||||
|
|
@ -28,7 +27,6 @@ internal class CreateWalletBackupModel @Inject constructor(
|
|||
|
||||
val params = paramsContainer.require<CreateWalletBackupComponent.Params>()
|
||||
|
||||
val hotWalletStepperComponentModelCallback = HotWalletStepperComponentModelCallback()
|
||||
val manualBackupStartModelCallbacks = ManualBackupStartModelCallbacks()
|
||||
val manualBackupPhraseModelCallbacks = ManualBackupPhraseModelCallbacks()
|
||||
val manualBackupCheckModelCallbacks = ManualBackupCheckModelCallbacks()
|
||||
|
|
@ -63,14 +61,6 @@ internal class CreateWalletBackupModel @Inject constructor(
|
|||
router.pop()
|
||||
}
|
||||
|
||||
inner class HotWalletStepperComponentModelCallback : HotWalletStepperComponent.ModelCallback {
|
||||
override fun onBackClick() {
|
||||
onBack()
|
||||
}
|
||||
|
||||
override fun onSkipClick() = Unit
|
||||
}
|
||||
|
||||
inner class ManualBackupStartModelCallbacks : ManualBackupStartComponent.ModelCallbacks {
|
||||
override fun onContinueClick() {
|
||||
onManualBackupStarted()
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
package com.tangem.features.hotwallet.createwalletbackup
|
||||
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.hotwallet.createwalletbackup.routing.CreateWalletBackupRoute
|
||||
import com.tangem.features.hotwallet.impl.R
|
||||
import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class CreateWalletBackupStepperStateManager @Inject constructor() {
|
||||
|
||||
fun getStepperState(route: CreateWalletBackupRoute): HotWalletStepperComponent.StepperUM? {
|
||||
return when (route) {
|
||||
is CreateWalletBackupRoute.RecoveryPhraseStart -> HotWalletStepperComponent.StepperUM(
|
||||
currentStep = STEP_START,
|
||||
steps = STEPS_COUNT,
|
||||
title = resourceReference(R.string.common_backup),
|
||||
showBackButton = true,
|
||||
showSkipButton = false,
|
||||
showFeedbackButton = true,
|
||||
)
|
||||
is CreateWalletBackupRoute.RecoveryPhrase -> HotWalletStepperComponent.StepperUM(
|
||||
currentStep = STEP_PHRASE,
|
||||
steps = STEPS_COUNT,
|
||||
title = resourceReference(R.string.common_backup),
|
||||
showBackButton = true,
|
||||
showSkipButton = false,
|
||||
showFeedbackButton = true,
|
||||
)
|
||||
is CreateWalletBackupRoute.ConfirmBackup -> HotWalletStepperComponent.StepperUM(
|
||||
currentStep = STEP_CONFIRM,
|
||||
steps = STEPS_COUNT,
|
||||
title = resourceReference(R.string.common_backup),
|
||||
showBackButton = true,
|
||||
showSkipButton = false,
|
||||
showFeedbackButton = true,
|
||||
)
|
||||
is CreateWalletBackupRoute.BackupCompleted -> HotWalletStepperComponent.StepperUM(
|
||||
currentStep = STEP_COMPLETED,
|
||||
steps = STEPS_COUNT,
|
||||
title = resourceReference(R.string.common_done),
|
||||
showBackButton = false,
|
||||
showSkipButton = false,
|
||||
showFeedbackButton = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val STEPS_COUNT = 4
|
||||
|
||||
private const val STEP_START = 1
|
||||
private const val STEP_PHRASE = 2
|
||||
private const val STEP_CONFIRM = 3
|
||||
private const val STEP_COMPLETED = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -13,9 +13,8 @@ import com.tangem.core.decompose.context.childByContext
|
|||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.hotwallet.CreateWalletBackupComponent
|
||||
import com.tangem.features.hotwallet.createwalletbackup.routing.CreateWalletBackupChildFactory
|
||||
import com.tangem.features.hotwallet.createwalletbackup.routing.CreateWalletBackupRoute
|
||||
import com.tangem.features.hotwallet.createwalletbackup.ui.CreateWalletBackupContent
|
||||
import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent
|
||||
import com.tangem.features.hotwallet.stepper.impl.DefaultHotWalletStepperComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -24,9 +23,7 @@ import kotlinx.coroutines.launch
|
|||
internal class DefaultCreateWalletBackupComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: CreateWalletBackupComponent.Params,
|
||||
private val stepperStateManager: CreateWalletBackupStepperStateManager,
|
||||
createWalletBackupChildFactory: CreateWalletBackupChildFactory,
|
||||
stepperComponentFactory: DefaultHotWalletStepperComponent.Factory,
|
||||
) : CreateWalletBackupComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: CreateWalletBackupModel = getOrCreateModel(params)
|
||||
|
|
@ -46,14 +43,6 @@ internal class DefaultCreateWalletBackupComponent @AssistedInject constructor(
|
|||
},
|
||||
)
|
||||
|
||||
private val stepperComponent = stepperComponentFactory.create(
|
||||
context = this,
|
||||
params = HotWalletStepperComponent.Params(
|
||||
initState = HotWalletStepperComponent.StepperUM.initialState(),
|
||||
callback = model.hotWalletStepperComponentModelCallback,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
innerStack.subscribe(
|
||||
lifecycle = lifecycle,
|
||||
|
|
@ -72,13 +61,11 @@ internal class DefaultCreateWalletBackupComponent @AssistedInject constructor(
|
|||
|
||||
BackHandler(onBack = model::onBack)
|
||||
|
||||
val stepperState = stepperStateManager.getStepperState(currentRoute)
|
||||
stepperState?.let { stepperComponent.updateState(it) }
|
||||
|
||||
CreateWalletBackupContent(
|
||||
stackState = stackState,
|
||||
stepperComponent = stepperComponent.takeIf { stepperState != null },
|
||||
modifier = modifier,
|
||||
showTopBar = currentRoute !is CreateWalletBackupRoute.BackupCompleted,
|
||||
onBackClick = model::onBack,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@ package com.tangem.features.hotwallet.createwalletbackup.di
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.hotwallet.CreateWalletBackupComponent
|
||||
import com.tangem.features.hotwallet.createwalletbackup.CreateWalletBackupModel
|
||||
import com.tangem.features.hotwallet.createwalletbackup.CreateWalletBackupStepperStateManager
|
||||
import com.tangem.features.hotwallet.createwalletbackup.DefaultCreateWalletBackupComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
|
|
@ -16,7 +14,7 @@ import javax.inject.Singleton
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface CreateWalletBackupModuleBinds {
|
||||
internal interface CreateWalletBackupModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
|
|
@ -28,15 +26,4 @@ internal interface CreateWalletBackupModuleBinds {
|
|||
@IntoMap
|
||||
@ClassKey(CreateWalletBackupModel::class)
|
||||
fun bindCreateWalletBackupModel(model: CreateWalletBackupModel): Model
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CreateWalletBackupModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCreateWalletBackupStepperStateManager(): CreateWalletBackupStepperStateManager {
|
||||
return CreateWalletBackupStepperStateManager()
|
||||
}
|
||||
}
|
||||
|
|
@ -11,15 +11,19 @@ import com.arkivanov.decompose.extensions.compose.stack.Children
|
|||
import com.arkivanov.decompose.extensions.compose.stack.animation.slide
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
|
||||
import com.arkivanov.decompose.router.stack.ChildStack
|
||||
import com.tangem.features.hotwallet.impl.R
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.hotwallet.createwalletbackup.routing.CreateWalletBackupRoute
|
||||
import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent
|
||||
|
||||
@Composable
|
||||
internal fun CreateWalletBackupContent(
|
||||
stackState: ChildStack<CreateWalletBackupRoute, ComposableContentComponent>,
|
||||
stepperComponent: HotWalletStepperComponent?,
|
||||
showTopBar: Boolean,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -29,7 +33,15 @@ internal fun CreateWalletBackupContent(
|
|||
.imePadding()
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
stepperComponent?.Content(Modifier)
|
||||
if (showTopBar) {
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier,
|
||||
startButton = TopAppBarButtonUM.Back(
|
||||
onBackClicked = onBackClick,
|
||||
),
|
||||
title = stringResourceSafe(id = R.string.common_backup),
|
||||
)
|
||||
}
|
||||
|
||||
Children(
|
||||
stack = stackState,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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.ComposableContentComponent
|
||||
import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.hotwallet.manualbackup.check.model.ManualBackupCheckModel
|
||||
import com.tangem.features.hotwallet.manualbackup.check.ui.ManualBackupCheckContent
|
||||
|
|
@ -22,6 +23,7 @@ internal class ManualBackupCheckComponent @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
DisableScreenshotsDisposableEffect()
|
||||
ManualBackupCheckContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ internal class ManualBackupCheckModel @Inject constructor(
|
|||
private val callbacks = params.callbacks
|
||||
|
||||
internal val uiState: StateFlow<ManualBackupCheckUM>
|
||||
field = MutableStateFlow(getInitialUIState())
|
||||
field = MutableStateFlow(getInitialUIState())
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ internal class ManualBackupCompletedModel @Inject constructor(
|
|||
private val params: ManualBackupCompletedComponent.Params = paramsContainer.require()
|
||||
|
||||
internal val uiState: StateFlow<ManualBackupCompletedUM>
|
||||
field = MutableStateFlow(
|
||||
ManualBackupCompletedUM(
|
||||
onContinueClick = { params.callbacks.onContinueClick(params.userWalletId) },
|
||||
),
|
||||
)
|
||||
field = MutableStateFlow(
|
||||
ManualBackupCompletedUM(
|
||||
onContinueClick = { params.callbacks.onContinueClick(params.userWalletId) },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -35,11 +35,11 @@ internal class ManualBackupPhraseModel @Inject constructor(
|
|||
private val callbacks = params.callbacks
|
||||
|
||||
internal val uiState: StateFlow<ManualBackupPhraseUM>
|
||||
field = MutableStateFlow(
|
||||
ManualBackupPhraseUM(
|
||||
onContinueClick = callbacks::onContinueClick,
|
||||
),
|
||||
)
|
||||
field = MutableStateFlow(
|
||||
ManualBackupPhraseUM(
|
||||
onContinueClick = callbacks::onContinueClick,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ internal class ManualBackupStartModel @Inject constructor(
|
|||
private val params: ManualBackupStartComponent.Params = paramsContainer.require()
|
||||
|
||||
internal val uiState: StateFlow<ManualBackupStartUM>
|
||||
field = MutableStateFlow(
|
||||
ManualBackupStartUM(
|
||||
onContinueClick = params.callbacks::onContinueClick,
|
||||
),
|
||||
)
|
||||
field = MutableStateFlow(
|
||||
ManualBackupStartUM(
|
||||
onContinueClick = params.callbacks::onContinueClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -18,9 +18,9 @@ internal class MobileWalletSetupFinishedModel @Inject constructor(
|
|||
private val params: MobileWalletSetupFinishedComponent.Params = paramsContainer.require()
|
||||
|
||||
internal val uiState: StateFlow<MobileWalletSetupFinishedUM>
|
||||
field = MutableStateFlow(
|
||||
MobileWalletSetupFinishedUM(
|
||||
onContinueClick = params.callbacks::onContinueClick,
|
||||
),
|
||||
)
|
||||
field = MutableStateFlow(
|
||||
MobileWalletSetupFinishedUM(
|
||||
onContinueClick = params.callbacks::onContinueClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ internal class HotWalletStepperModel @Inject constructor(
|
|||
val params = paramsContainer.require<HotWalletStepperComponent.Params>()
|
||||
|
||||
val uiState: StateFlow<HotWalletStepperComponent.StepperUM>
|
||||
field = MutableStateFlow(params.initState)
|
||||
field = MutableStateFlow(params.initState)
|
||||
|
||||
fun updateState(newState: HotWalletStepperComponent.StepperUM) {
|
||||
uiState.value = newState
|
||||
|
|
|
|||
|
|
@ -35,11 +35,11 @@ internal class ViewPhraseModel @Inject constructor(
|
|||
private val params = paramsContainer.require<ViewPhraseComponent.Params>()
|
||||
|
||||
internal val uiState: StateFlow<ViewPhraseUM>
|
||||
field = MutableStateFlow(
|
||||
ViewPhraseUM(
|
||||
onBackClick = { router.pop() },
|
||||
),
|
||||
)
|
||||
field = MutableStateFlow(
|
||||
ViewPhraseUM(
|
||||
onBackClick = { router.pop() },
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
loadSeedPhrase()
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ internal class WalletActivationModel @Inject constructor(
|
|||
is WalletActivationRoute.ManualBackupCheck -> stackNavigation.pop()
|
||||
is WalletActivationRoute.ManualBackupCompleted -> Unit
|
||||
is WalletActivationRoute.SetAccessCode -> Unit
|
||||
is WalletActivationRoute.ConfirmAccessCode -> Unit
|
||||
is WalletActivationRoute.ConfirmAccessCode -> stackNavigation.pop()
|
||||
is WalletActivationRoute.PushNotifications -> Unit
|
||||
is WalletActivationRoute.SetupFinished -> Unit
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import javax.inject.Inject
|
|||
@ModelScoped
|
||||
internal class WalletBackupModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
private val getWalletUseCase: GetUserWalletUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val unlockHotWalletContextualUseCase: UnlockHotWalletContextualUseCase,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
|
|
@ -33,35 +33,37 @@ internal class WalletBackupModel @Inject constructor(
|
|||
private val params: WalletBackupComponent.Params = paramsContainer.require()
|
||||
|
||||
val uiState: StateFlow<WalletBackupUM>
|
||||
field = MutableStateFlow(
|
||||
WalletBackupUM(
|
||||
onBackClick = { router.pop() },
|
||||
recoveryPhraseOption = LabelUM(
|
||||
text = resourceReference(R.string.hw_backup_no_backup),
|
||||
style = LabelStyle.WARNING,
|
||||
field = MutableStateFlow(
|
||||
WalletBackupUM(
|
||||
onBackClick = { router.pop() },
|
||||
recoveryPhraseOption = LabelUM(
|
||||
text = resourceReference(R.string.hw_backup_no_backup),
|
||||
style = LabelStyle.WARNING,
|
||||
),
|
||||
googleDriveOption = LabelUM(
|
||||
text = resourceReference(R.string.common_coming_soon),
|
||||
style = LabelStyle.REGULAR,
|
||||
),
|
||||
googleDriveStatus = BackupStatus.ComingSoon,
|
||||
onRecoveryPhraseClick = ::onRecoveryPhraseClick,
|
||||
onGoogleDriveClick = { },
|
||||
onHardwareWalletClick = ::onHardwareWalletClick,
|
||||
backedUp = false,
|
||||
),
|
||||
googleDriveOption = LabelUM(
|
||||
text = resourceReference(R.string.common_coming_soon),
|
||||
style = LabelStyle.REGULAR,
|
||||
),
|
||||
googleDriveStatus = BackupStatus.ComingSoon,
|
||||
onRecoveryPhraseClick = ::onRecoveryPhraseClick,
|
||||
onGoogleDriveClick = { },
|
||||
onHardwareWalletClick = ::onHardwareWalletClick,
|
||||
backedUp = false,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
init {
|
||||
getWalletUseCase.invoke(params.userWalletId)
|
||||
.fold(
|
||||
ifLeft = {
|
||||
Timber.e("Error on getting user wallet: $it")
|
||||
},
|
||||
ifRight = {
|
||||
updateBackupStatuses(it)
|
||||
},
|
||||
)
|
||||
getUserWalletUseCase.invokeFlow(params.userWalletId)
|
||||
.onEach { either ->
|
||||
either.fold(
|
||||
ifLeft = {
|
||||
Timber.e("Error on getting user wallet: $it")
|
||||
},
|
||||
ifRight = {
|
||||
updateBackupStatuses(it)
|
||||
},
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun updateBackupStatuses(userWallet: UserWallet) {
|
||||
|
|
@ -95,7 +97,7 @@ internal class WalletBackupModel @Inject constructor(
|
|||
|
||||
private fun onRecoveryPhraseClick() {
|
||||
if (uiState.value.backedUp) {
|
||||
getWalletUseCase.invoke(params.userWalletId)
|
||||
getUserWalletUseCase.invoke(params.userWalletId)
|
||||
.fold(
|
||||
ifLeft = {
|
||||
Timber.e("Error on getting user wallet: $it")
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
package com.tangem.features.kyc
|
||||
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface KycComponent {
|
||||
interface KycComponent : ComposableContentComponent {
|
||||
|
||||
fun launch()
|
||||
data object Params
|
||||
|
||||
interface Factory {
|
||||
fun create(appComponentContext: AppComponentContext): KycComponent
|
||||
}
|
||||
interface Factory : ComponentFactory<Params, KycComponent>
|
||||
}
|
||||
|
|
@ -1,45 +1,62 @@
|
|||
package com.tangem.features.kyc
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.sumsub.sns.core.SNSMobileSDK
|
||||
import com.sumsub.sns.core.data.listener.TokenExpirationHandler
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.domain.pay.KycStartInfo
|
||||
import com.tangem.features.kyc.theme.TangemSNSIconHandler
|
||||
import com.tangem.features.kyc.theme.TangemSNSTheme
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
|
||||
class DefaultKycComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: KycComponent.Params,
|
||||
) : KycComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: DefaultKycModel = getOrCreateModel()
|
||||
private val model: DefaultKycModel = getOrCreateModel(params)
|
||||
|
||||
override fun launch() {
|
||||
init {
|
||||
componentScope.launch {
|
||||
model.uiState.collect {
|
||||
it?.let { startInfo ->
|
||||
val tokenExpirationHandler = object : TokenExpirationHandler {
|
||||
override fun onTokenExpired() = ""
|
||||
}
|
||||
val snsSdk = SNSMobileSDK.Builder(activity)
|
||||
.withAccessToken(accessToken = startInfo.token, onTokenExpiration = tokenExpirationHandler)
|
||||
.withTheme(TangemSNSTheme.theme(activity))
|
||||
.withIconHandler(TangemSNSIconHandler())
|
||||
.withLocale(Locale(startInfo.locale))
|
||||
.build()
|
||||
snsSdk.launch()
|
||||
}
|
||||
model.uiState.drop(1).collectLatest { startInfo ->
|
||||
startInfo?.let { launchSdk(startInfo) }
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
model.getKycToken()
|
||||
}
|
||||
|
||||
private fun launchSdk(startInfo: KycStartInfo) {
|
||||
val tokenExpirationHandler = object : TokenExpirationHandler {
|
||||
/**
|
||||
* We don't refresh this token for f&f release. Assume it lives long enough to finish KYC
|
||||
* [REDACTED_TODO_COMMENT]
|
||||
*/
|
||||
override fun onTokenExpired() = ""
|
||||
}
|
||||
val snsSdk = SNSMobileSDK.Builder(activity)
|
||||
.withAccessToken(accessToken = startInfo.token, onTokenExpiration = tokenExpirationHandler)
|
||||
.withTheme(TangemSNSTheme.theme(activity))
|
||||
.withIconHandler(TangemSNSIconHandler())
|
||||
.withLocale(Locale(startInfo.locale))
|
||||
.build()
|
||||
snsSdk.launch()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
KycLoadingScreen(router::pop, modifier)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : KycComponent.Factory {
|
||||
override fun create(appComponentContext: AppComponentContext): DefaultKycComponent
|
||||
override fun create(context: AppComponentContext, params: KycComponent.Params): DefaultKycComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ class DefaultKycModel @Inject constructor(
|
|||
private val _uiState: MutableStateFlow<KycStartInfo?> = MutableStateFlow(null)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
fun getKycToken() {
|
||||
init {
|
||||
modelScope.launch {
|
||||
kycRepository.getKycStartInfo().getOrNull()?.let { _uiState.emit(it) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.features.kyc
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun KycLoadingScreen(onBack: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
AppBarWithBackButton(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
onBackClick = onBack,
|
||||
iconRes = R.drawable.ic_back_24,
|
||||
)
|
||||
},
|
||||
content = { paddingValues ->
|
||||
Box(
|
||||
modifier = modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier,
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -15,6 +15,8 @@ dependencies {
|
|||
implementation(projects.features.kyc.api)
|
||||
|
||||
implementation(projects.core.decompose)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.features.kyc
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -7,17 +9,17 @@ import dagger.assisted.AssistedInject
|
|||
|
||||
/**
|
||||
* Mocking it for release/external builds to exclude SumSub dependency
|
||||
* This will never be called if the FT [isTangemPayEnabled] is off
|
||||
*/
|
||||
internal class MockKycComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
) : KycComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
override fun launch() {
|
||||
/* no op */
|
||||
}
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) { /* no op */ }
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : KycComponent.Factory {
|
||||
override fun create(appComponentContext: AppComponentContext): MockKycComponent
|
||||
override fun create(context: AppComponentContext, params: KycComponent.Params): MockKycComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ internal class ChooseManagedTokensModel @Inject constructor(
|
|||
|
||||
val bottomSheetNavigation: SlotNavigation<ChooseManageTokensBottomSheetConfig> = SlotNavigation()
|
||||
val uiState: StateFlow<ChooseManagedTokenUM>
|
||||
field = MutableStateFlow<ChooseManagedTokenUM>(createReadContentModel())
|
||||
field = MutableStateFlow<ChooseManagedTokenUM>(createReadContentModel())
|
||||
|
||||
init {
|
||||
manageTokensListManager.uiItems
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ internal class ManageTokensListManager @AssistedInject constructor(
|
|||
.distinctUntilChanged()
|
||||
val uiItems: Flow<ImmutableList<CurrencyItemUM>> = uiManager.items
|
||||
|
||||
/**
|
||||
* Launch pagination flow to get currencies
|
||||
*
|
||||
* @param isCollapsed set initial display state of networks. !!! WARNING !!! Use `false` flag with cation
|
||||
*/
|
||||
suspend fun launchPagination(isCollapsed: Boolean) = coroutineScope {
|
||||
val loadUserTokensFromRemote = when (mode) {
|
||||
is ManageTokensMode.Wallet -> source == ManageTokensSource.ONBOARDING
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ internal fun MarketsTokenDetailsContent(
|
|||
onBackClick: () -> Unit,
|
||||
onHeaderSizeChange: (Dp) -> Unit,
|
||||
backButtonEnabled: Boolean,
|
||||
portfolioBlock: @Composable ((Modifier) -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
portfolioBlock: @Composable ((Modifier) -> Unit)?,
|
||||
) {
|
||||
Content(
|
||||
modifier = modifier,
|
||||
|
|
@ -88,8 +88,8 @@ private fun Content(
|
|||
onBackClick: () -> Unit,
|
||||
onHeaderSizeChange: (Dp) -> Unit,
|
||||
backButtonEnabled: Boolean,
|
||||
portfolioBlock: @Composable ((Modifier) -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
portfolioBlock: @Composable ((Modifier) -> Unit)?,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.icons.IconTint
|
||||
import com.tangem.core.ui.components.token.TokenItem
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
|
|
@ -100,9 +101,9 @@ private class PortfolioTokenUMProvider : CollectionPreviewParameterProvider<Port
|
|||
tokenItemState = (tokenUM.tokenItemState as TokenItemState.Content).copy(
|
||||
fiatAmountState = contentFiatAmount.copy(
|
||||
icons = persistentListOf(
|
||||
TokenItemState.FiatAmountState.Content.IconUM(
|
||||
TokenFiatAmountState.Content.IconUM(
|
||||
iconRes = R.drawable.ic_staking_24,
|
||||
useAccentColor = true,
|
||||
tint = IconTint.Accent,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -185,9 +185,9 @@ internal fun NFTDetailsGroupBlock(
|
|||
@Composable
|
||||
internal fun NFTBlocksGroupAction(
|
||||
text: TextReference,
|
||||
startIcon: @Composable RowScope.() -> Unit,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
startIcon: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ interface NFTSendSuccessListener {
|
|||
internal class DefaultNFTSendSuccessTrigger @Inject constructor() : NFTSendSuccessTrigger, NFTSendSuccessListener {
|
||||
|
||||
override val nftSendSuccessFlow: SharedFlow<Unit>
|
||||
field = MutableSharedFlow<Unit>()
|
||||
field = MutableSharedFlow<Unit>()
|
||||
|
||||
override suspend fun triggerSuccessNFTSend() {
|
||||
nftSendSuccessFlow.emit(Unit)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.arkivanov.decompose.router.stack.StackNavigation
|
|||
import com.arkivanov.decompose.router.stack.replaceAll
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -13,12 +12,13 @@ import com.tangem.core.decompose.ui.UiMessageSender
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.onboarding.v2.TitleProvider
|
||||
|
|
@ -219,7 +219,7 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
|
||||
// legacy flow
|
||||
if (userWalletsListManager.hasUserWallets) {
|
||||
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync!! }.getOrElse { false }
|
||||
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLocked!! }.getOrElse { false }
|
||||
|
||||
if (isLocked) {
|
||||
router.replaceAll(AppRoute.Welcome())
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute
|
|||
|
||||
@Composable
|
||||
internal inline fun OnboardingEntry(
|
||||
modifier: Modifier = Modifier,
|
||||
childStack: ChildStack<OnboardingRoute, Any>,
|
||||
modifier: Modifier = Modifier,
|
||||
stepperContent: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -19,6 +20,7 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM
|
||||
|
||||
|
|
@ -60,7 +62,9 @@ internal fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier:
|
|||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp)
|
||||
.testTag(StoriesScreenTestTags.TITLE),
|
||||
)
|
||||
|
||||
Text(
|
||||
|
|
@ -68,7 +72,9 @@ internal fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier:
|
|||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 12.dp),
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.testTag(StoriesScreenTestTags.TEXT),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,13 +58,17 @@ internal class ImportSeedPhraseUiStateBuilder(
|
|||
val text = st.words.text
|
||||
val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
|
||||
val newWords = wordsFromText.dropLast(1) + word
|
||||
val newWordsText = newWords.joinToString(" ")
|
||||
st.copy(
|
||||
words = TextFieldValue(
|
||||
text = newWordsText,
|
||||
selection = TextRange(newWordsText.length),
|
||||
),
|
||||
val newWordsText = newWords.joinToString(" ").plus(" ")
|
||||
val newWordsState = TextFieldValue(
|
||||
text = newWordsText,
|
||||
selection = TextRange(newWordsText.length),
|
||||
)
|
||||
st.copy(
|
||||
words = newWordsState,
|
||||
).also {
|
||||
launchInterceptWords(wordsField = newWordsState)
|
||||
suggestNextWord(newWordsState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -25,6 +26,7 @@ import com.tangem.core.ui.components.SpacerW
|
|||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.SelectPaymentMethodBottomSheetTestTags
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.model.PaymentMethodType
|
||||
import com.tangem.features.onramp.alloffers.entity.AllOffersPaymentMethodUM
|
||||
|
|
@ -74,10 +76,13 @@ private fun PaymentMethod(methodUM: AllOffersPaymentMethodUM, modifier: Modifier
|
|||
end = 12.dp,
|
||||
top = 14.dp,
|
||||
bottom = 12.dp,
|
||||
),
|
||||
)
|
||||
.testTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD),
|
||||
) {
|
||||
PaymentMethodIcon(
|
||||
modifier = Modifier.size(36.dp),
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.testTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD_ICON),
|
||||
imageUrl = methodUM.methodConfig.method.imageUrl,
|
||||
)
|
||||
|
||||
|
|
@ -111,6 +116,7 @@ private fun PaymentMethodInfoBlock(
|
|||
text = paymentMethodName,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.testTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD_NAME),
|
||||
)
|
||||
SpacerH(2.dp)
|
||||
Row(
|
||||
|
|
@ -121,12 +127,14 @@ private fun PaymentMethodInfoBlock(
|
|||
text = stringResourceSafe(R.string.onramp_up_to_rate),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.testTag(SelectPaymentMethodBottomSheetTestTags.UP_TO_TEXT),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = rate,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.testTag(SelectPaymentMethodBottomSheetTestTags.TOKEN_AMOUNT),
|
||||
)
|
||||
|
||||
when {
|
||||
|
|
@ -134,6 +142,7 @@ private fun PaymentMethodInfoBlock(
|
|||
Image(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_best_rate_12),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.testTag(SelectPaymentMethodBottomSheetTestTags.BEST_RATE_ICON),
|
||||
)
|
||||
}
|
||||
diff != null -> {
|
||||
|
|
@ -158,7 +167,9 @@ private fun PaymentMethodInfoBlock(
|
|||
private fun ProvidersCountBlockInfo(providersCount: Int) {
|
||||
BorderedRow {
|
||||
Icon(
|
||||
modifier = Modifier.size(10.dp),
|
||||
modifier = Modifier
|
||||
.size(10.dp)
|
||||
.testTag(SelectPaymentMethodBottomSheetTestTags.PROVIDER_COUNT_ICON),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_clock_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
|
|
@ -172,6 +183,7 @@ private fun ProvidersCountBlockInfo(providersCount: Int) {
|
|||
),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.testTag(SelectPaymentMethodBottomSheetTestTags.PROVIDER_COUNT_TEXT),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -180,7 +192,9 @@ private fun ProvidersCountBlockInfo(providersCount: Int) {
|
|||
private fun TimingBlockInfo(speed: PaymentMethodType.PaymentSpeed) {
|
||||
BorderedRow {
|
||||
Icon(
|
||||
modifier = Modifier.size(10.dp),
|
||||
modifier = Modifier
|
||||
.size(10.dp)
|
||||
.testTag(SelectPaymentMethodBottomSheetTestTags.TIMING_ICON),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_staking_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -16,7 +15,6 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.extensions.appendColored
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.BuyTokenDetailsScreenTestTags
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.main.entity.OnrampMainComponentUM
|
||||
import com.tangem.features.onramp.main.entity.OnrampProviderBlockUM
|
||||
|
|
@ -79,7 +77,6 @@ private fun OnrampTosText(provider: OnrampProviderBlockUM.Content?) {
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
),
|
||||
modifier = Modifier.testTag(BuyTokenDetailsScreenTestTags.TOS_BLOCK),
|
||||
onClick = { offset ->
|
||||
clickableAnnotation.getStringAnnotations(
|
||||
tag = TERMS_OF_USE_KEY,
|
||||
|
|
|
|||
|
|
@ -13,14 +13,12 @@ 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.platform.testTag
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import com.tangem.core.ui.extensions.appendSpace
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.BuyTokenDetailsScreenTestTags
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.main.entity.OnrampProviderBlockUM
|
||||
import com.tangem.features.onramp.paymentmethod.ui.PaymentMethodIcon
|
||||
|
|
@ -62,7 +60,6 @@ private fun OnrampProviderBlock(state: OnrampProviderBlockUM.Content, modifier:
|
|||
},
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
modifier = Modifier.testTag(BuyTokenDetailsScreenTestTags.PROVIDER_TITLE),
|
||||
)
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
|
|
@ -72,7 +69,6 @@ private fun OnrampProviderBlock(state: OnrampProviderBlockUM.Content, modifier:
|
|||
},
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.testTag(BuyTokenDetailsScreenTestTags.PROVIDER_TEXT),
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
|
|
@ -112,7 +108,6 @@ private fun OnrampProviderLoading(modifier: Modifier = Modifier) {
|
|||
text = stringResourceSafe(id = R.string.express_provider),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.testTag(BuyTokenDetailsScreenTestTags.PROVIDER_LOADING_TITLE),
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
@ -127,7 +122,6 @@ private fun OnrampProviderLoading(modifier: Modifier = Modifier) {
|
|||
text = stringResourceSafe(id = R.string.express_fetch_best_rates),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.testTag(BuyTokenDetailsScreenTestTags.PROVIDER_LOADING_TEXT),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import androidx.compose.ui.draw.drawWithCache
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -30,6 +31,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
|||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.OnrampOffersBlockTestTags
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.model.PaymentMethodType
|
||||
import com.tangem.features.onramp.impl.R
|
||||
|
|
@ -156,11 +158,13 @@ private fun OfferHeader(advantage: OnrampOfferAdvantagesUM) {
|
|||
imageVector = ImageVector.vectorResource(R.drawable.ic_best_rate_16),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.testTag(OnrampOffersBlockTestTags.BEST_RATE_ICON),
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.express_provider_best_rate),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier.testTag(OnrampOffersBlockTestTags.BEST_RATE_TITLE),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -194,6 +198,7 @@ private fun RateBlock(rate: String, diff: TextReference?) {
|
|||
text = rate,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.testTag(OnrampOffersBlockTestTags.OFFER_TOKEN_AMOUNT),
|
||||
)
|
||||
diff?.let {
|
||||
Text(
|
||||
|
|
@ -215,7 +220,9 @@ private fun RateBlock(rate: String, diff: TextReference?) {
|
|||
private fun PaymentBlockInOffer(paymentMethod: OnrampPaymentMethod, providerName: String) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp),
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.testTag(OnrampOffersBlockTestTags.TIMING_ICON),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_clock_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
|
|
@ -235,6 +242,7 @@ private fun PaymentBlockInOffer(paymentMethod: OnrampPaymentMethod, providerName
|
|||
text = providerName,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.testTag(OnrampOffersBlockTestTags.PROVIDER_NAME),
|
||||
)
|
||||
|
||||
SpacerWMax()
|
||||
|
|
@ -243,12 +251,15 @@ private fun PaymentBlockInOffer(paymentMethod: OnrampPaymentMethod, providerName
|
|||
text = stringResourceSafe(R.string.onramp_pay_with),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.testTag(OnrampOffersBlockTestTags.PAY_WITH),
|
||||
)
|
||||
|
||||
SpacerW(4.dp)
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier.sizeIn(maxWidth = 38.dp, maxHeight = 16.dp),
|
||||
modifier = Modifier
|
||||
.sizeIn(maxWidth = 38.dp, maxHeight = 16.dp)
|
||||
.testTag(OnrampOffersBlockTestTags.PAYMENT_METHOD_ICON),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(paymentMethod.imageUrl)
|
||||
.crossfade(enable = true)
|
||||
|
|
@ -298,6 +309,7 @@ internal fun TimingBlock(speed: PaymentMethodType.PaymentSpeed) {
|
|||
text = timingText,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.testTag(OnrampOffersBlockTestTags.TIMING_TEXT),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,13 +11,11 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.selectedBorder
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.SelectPaymentMethodBottomSheetTestTags
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodUM
|
||||
import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodsBottomSheetConfig
|
||||
|
|
@ -51,7 +49,7 @@ private fun SelectPaymentMethodBottomSheetContent(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.testTag(SelectPaymentMethodBottomSheetTestTags.LAZY_LIST),
|
||||
modifier = modifier,
|
||||
) {
|
||||
items(
|
||||
items = methods,
|
||||
|
|
@ -90,7 +88,6 @@ private fun PaymentMethodItem(paymentMethod: PaymentMethodUM, isSelected: Boolea
|
|||
) {
|
||||
PaymentMethodIcon(
|
||||
imageUrl = paymentMethod.imageUrl,
|
||||
modifier = Modifier.testTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD_ICON),
|
||||
)
|
||||
Text(
|
||||
text = paymentMethod.name,
|
||||
|
|
|
|||
|
|
@ -9,5 +9,4 @@ internal enum class OnrampOperation {
|
|||
BUY,
|
||||
SELL,
|
||||
SWAP,
|
||||
;
|
||||
}
|
||||
|
|
@ -53,20 +53,18 @@ internal fun PushNotificationsContent(
|
|||
|
||||
Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
ShowcaseContent(
|
||||
headerIconRes = R.drawable.ic_notifications_unread_24,
|
||||
headerIconRes = R.drawable.ic_notification_56,
|
||||
headerText = resourceReference(R.string.user_push_notification_agreement_header),
|
||||
showcaseItems = persistentListOf(
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_rocket_launch_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_one),
|
||||
iconRes = R.drawable.ic_notification_square_24,
|
||||
title = resourceReference(R.string.user_push_notification_agreement_argument_one_title),
|
||||
subTitle = resourceReference(R.string.user_push_notification_agreement_argument_one_subtitle),
|
||||
),
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_storefront_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_two),
|
||||
),
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_notifications_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_three),
|
||||
iconRes = R.drawable.ic_stars_24,
|
||||
title = resourceReference(R.string.user_push_notification_agreement_argument_two_title),
|
||||
subTitle = resourceReference(R.string.user_push_notification_agreement_argument_two_subtitle),
|
||||
),
|
||||
),
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing40),
|
||||
|
|
|
|||
|
|
@ -26,20 +26,18 @@ internal fun PushNotificationsScreen(
|
|||
)
|
||||
|
||||
Showcase(
|
||||
headerIconRes = R.drawable.ic_notifications_unread_24,
|
||||
headerIconRes = R.drawable.ic_notification_56,
|
||||
headerText = resourceReference(R.string.user_push_notification_agreement_header),
|
||||
showcaseItems = persistentListOf(
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_rocket_launch_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_one),
|
||||
iconRes = R.drawable.ic_notification_square_24,
|
||||
title = resourceReference(R.string.user_push_notification_agreement_argument_one_title),
|
||||
subTitle = resourceReference(R.string.user_push_notification_agreement_argument_one_subtitle),
|
||||
),
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_storefront_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_two),
|
||||
),
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_notifications_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_three),
|
||||
iconRes = R.drawable.ic_stars_24,
|
||||
title = resourceReference(R.string.user_push_notification_agreement_argument_two_title),
|
||||
subTitle = resourceReference(R.string.user_push_notification_agreement_argument_two_subtitle),
|
||||
),
|
||||
),
|
||||
primaryButton = ShowcaseButtonModel(
|
||||
|
|
|
|||
|
|
@ -18,22 +18,50 @@ sealed class CommonSendAnalyticEvents(
|
|||
/** Recipient address screen opened */
|
||||
data class AddressScreenOpened(
|
||||
val categoryName: String,
|
||||
) : CommonSendAnalyticEvents(category = categoryName, event = "Address Screen Opened")
|
||||
val source: CommonSendSource,
|
||||
) : CommonSendAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Address Screen Opened",
|
||||
params = mapOf(
|
||||
SOURCE to source.analyticsName,
|
||||
),
|
||||
)
|
||||
|
||||
/** Amount screen opened */
|
||||
data class AmountScreenOpened(
|
||||
val categoryName: String,
|
||||
) : CommonSendAnalyticEvents(category = categoryName, event = "Amount Screen Opened")
|
||||
val source: CommonSendSource,
|
||||
) : CommonSendAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Amount Screen Opened",
|
||||
params = mapOf(
|
||||
SOURCE to source.analyticsName,
|
||||
),
|
||||
)
|
||||
|
||||
/** Fee screen opened */
|
||||
data class FeeScreenOpened(
|
||||
val categoryName: String,
|
||||
) : CommonSendAnalyticEvents(category = categoryName, event = "Fee Screen Opened")
|
||||
val source: CommonSendSource,
|
||||
) : CommonSendAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Fee Screen Opened",
|
||||
params = mapOf(
|
||||
SOURCE to source.analyticsName,
|
||||
),
|
||||
)
|
||||
|
||||
/** Confirmation screen opened */
|
||||
data class ConfirmationScreenOpened(
|
||||
val categoryName: String,
|
||||
) : CommonSendAnalyticEvents(category = categoryName, event = "Confirm Screen Opened")
|
||||
val source: CommonSendSource,
|
||||
) : CommonSendAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Confirm Screen Opened",
|
||||
params = mapOf(
|
||||
SOURCE to source.analyticsName,
|
||||
),
|
||||
)
|
||||
|
||||
/** If transaction delays notification is present */
|
||||
data class NoticeTransactionDelays(
|
||||
|
|
@ -142,4 +170,11 @@ sealed class CommonSendAnalyticEvents(
|
|||
Fee,
|
||||
Confirm,
|
||||
}
|
||||
|
||||
enum class CommonSendSource(val analyticsName: String) {
|
||||
Send("Send"),
|
||||
SendWithSwap("Send&Swap"),
|
||||
WalletConnect("WalletConnect"),
|
||||
NFT("NFT"),
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ sealed class FeeSelectorParams {
|
|||
abstract val feeStateConfiguration: FeeStateConfiguration
|
||||
abstract val feeDisplaySource: FeeDisplaySource
|
||||
abstract val analyticsCategoryName: String
|
||||
abstract val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource
|
||||
|
||||
data class FeeSelectorBlockParams(
|
||||
override val state: FeeSelectorUM,
|
||||
|
|
@ -26,6 +28,7 @@ sealed class FeeSelectorParams {
|
|||
override val feeStateConfiguration: FeeStateConfiguration,
|
||||
override val feeDisplaySource: FeeDisplaySource,
|
||||
override val analyticsCategoryName: String,
|
||||
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
) : FeeSelectorParams()
|
||||
|
||||
data class FeeSelectorDetailsParams(
|
||||
|
|
@ -36,6 +39,7 @@ sealed class FeeSelectorParams {
|
|||
override val feeStateConfiguration: FeeStateConfiguration,
|
||||
override val feeDisplaySource: FeeDisplaySource,
|
||||
override val analyticsCategoryName: String,
|
||||
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
val callback: FeeSelectorModelCallback,
|
||||
) : FeeSelectorParams()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ package com.tangem.features.send.v2.api.subcomponents.amount.analytics
|
|||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
|
||||
sealed class CommonSendAmountAnalyticEvents(
|
||||
category: String,
|
||||
|
|
@ -26,12 +28,14 @@ sealed class CommonSendAmountAnalyticEvents(
|
|||
val categoryName: String,
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
val source: CommonSendAnalyticEvents.CommonSendSource,
|
||||
) : CommonSendAmountAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Max Amount Taped",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
BLOCKCHAIN to blockchain,
|
||||
SOURCE to source.analyticsName,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.send.v2.api.subcomponents.destination
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entity.PredefinedValues
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -12,12 +13,14 @@ sealed class SendDestinationComponentParams {
|
|||
|
||||
abstract val state: DestinationUM
|
||||
abstract val analyticsCategoryName: String
|
||||
abstract val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource
|
||||
abstract val userWalletId: UserWalletId
|
||||
abstract val cryptoCurrency: CryptoCurrency
|
||||
|
||||
data class DestinationParams(
|
||||
override val state: DestinationUM,
|
||||
override val analyticsCategoryName: String,
|
||||
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
override val cryptoCurrency: CryptoCurrency,
|
||||
override val userWalletId: UserWalletId,
|
||||
val title: TextReference,
|
||||
|
|
@ -29,6 +32,7 @@ sealed class SendDestinationComponentParams {
|
|||
data class DestinationBlockParams(
|
||||
override val state: DestinationUM,
|
||||
override val analyticsCategoryName: String,
|
||||
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
override val userWalletId: UserWalletId,
|
||||
override val cryptoCurrency: CryptoCurrency,
|
||||
val blockClickEnableFlow: StateFlow<Boolean>,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package com.tangem.features.send.v2.api.subcomponents.feeSelector.analytics
|
|||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.CommonSendSource
|
||||
|
||||
sealed class CommonSendFeeAnalyticEvents(
|
||||
category: String,
|
||||
|
|
@ -15,10 +18,14 @@ sealed class CommonSendFeeAnalyticEvents(
|
|||
data class SelectedFee(
|
||||
override val categoryName: String,
|
||||
val feeType: AnalyticsParam.FeeType,
|
||||
val source: CommonSendSource,
|
||||
) : CommonSendFeeAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Fee Selected",
|
||||
params = mapOf("Fee Type" to feeType.value),
|
||||
params = mapOf(
|
||||
FEE_TYPE to feeType.value,
|
||||
SOURCE to source.analyticsName,
|
||||
),
|
||||
)
|
||||
|
||||
/** Custom fee selected */
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
|
|||
feeStateConfiguration = params.feeStateConfiguration,
|
||||
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
analyticsSendSource = params.analyticsSendSource,
|
||||
),
|
||||
onDismiss = {
|
||||
model.feeSelectorBottomSheet.dismiss()
|
||||
|
|
|
|||
|
|
@ -18,16 +18,16 @@ internal class DefaultFeeSelectorReloadTrigger @Inject constructor() :
|
|||
FeeSelectorCheckReloadListener {
|
||||
|
||||
override val reloadTriggerFlow: SharedFlow<FeeSelectorData>
|
||||
field = MutableSharedFlow()
|
||||
field = MutableSharedFlow()
|
||||
|
||||
override val loadingStateTriggerFlow: SharedFlow<Unit>
|
||||
field = MutableSharedFlow()
|
||||
field = MutableSharedFlow()
|
||||
|
||||
override val checkReloadTriggerFlow: SharedFlow<Unit>
|
||||
field = MutableSharedFlow()
|
||||
field = MutableSharedFlow()
|
||||
|
||||
override val checkReloadResultFlow: SharedFlow<Boolean>
|
||||
field = MutableSharedFlow()
|
||||
field = MutableSharedFlow()
|
||||
|
||||
override suspend fun triggerUpdate(feeData: FeeSelectorData) {
|
||||
reloadTriggerFlow.emit(feeData)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorRelo
|
|||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.analytics.CommonSendFeeAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.analytics.CommonSendFeeAnalyticEvents.GasPriceInserter
|
||||
import com.tangem.features.send.v2.feeselector.model.transformers.*
|
||||
import com.tangem.utils.TangemLinks
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -36,7 +37,6 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -62,7 +62,7 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
val feeSelectorBottomSheet = SlotNavigation<Unit>()
|
||||
|
||||
val uiState: StateFlow<FeeSelectorUM>
|
||||
field = MutableStateFlow<FeeSelectorUM>(params.state)
|
||||
field = MutableStateFlow<FeeSelectorUM>(params.state)
|
||||
|
||||
init {
|
||||
initAppCurrency()
|
||||
|
|
@ -77,13 +77,7 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun onReadMoreClicked() {
|
||||
val locale = if (Locale.getDefault().language == RU_LOCALE) RU_LOCALE else EN_LOCALE
|
||||
val url = buildString {
|
||||
append(FEE_READ_MORE_URL_FIRST_PART)
|
||||
append(locale)
|
||||
append(FEE_READ_MORE_URL_SECOND_PART)
|
||||
}
|
||||
urlOpener.openUrl(url)
|
||||
urlOpener.openUrl(TangemLinks.FEE_BLOG_LINK)
|
||||
}
|
||||
|
||||
private fun initAppCurrency() {
|
||||
|
|
@ -156,6 +150,7 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
CommonSendFeeAnalyticEvents.SelectedFee(
|
||||
categoryName = params.analyticsCategoryName,
|
||||
feeType = feeSelectorUM.toAnalyticType(),
|
||||
source = params.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
val isCustomFeeEdited = feeSelectorUM.selectedFeeItem.fee.amount.value != feeSelectorUM.fees.normal.amount.value
|
||||
|
|
@ -182,7 +177,10 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
|
||||
fun showFeeSelector() {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.FeeScreenOpened(categoryName = params.analyticsCategoryName),
|
||||
CommonSendAnalyticEvents.FeeScreenOpened(
|
||||
categoryName = params.analyticsCategoryName,
|
||||
source = params.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.ScreenReopened(
|
||||
|
|
@ -245,11 +243,4 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val RU_LOCALE = "ru"
|
||||
const val EN_LOCALE = "en"
|
||||
const val FEE_READ_MORE_URL_FIRST_PART = "https://tangem.com/"
|
||||
const val FEE_READ_MORE_URL_SECOND_PART = "/blog/post/what-is-a-transaction-fee-and-why-do-we-need-it/"
|
||||
}
|
||||
}
|
||||
|
|
@ -93,6 +93,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.ConfirmationScreenOpened(
|
||||
categoryName = model.analyticCategoryName,
|
||||
source = model.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
if (model.currentRoute.value.isEditMode) {
|
||||
|
|
@ -101,19 +102,28 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
}
|
||||
is SendAmountComponent -> {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.AmountScreenOpened(categoryName = model.analyticCategoryName),
|
||||
CommonSendAnalyticEvents.AmountScreenOpened(
|
||||
categoryName = model.analyticCategoryName,
|
||||
source = model.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
activeComponent.updateState(model.uiState.value.amountUM)
|
||||
}
|
||||
is DefaultSendDestinationComponent -> {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.AddressScreenOpened(categoryName = model.analyticCategoryName),
|
||||
CommonSendAnalyticEvents.AddressScreenOpened(
|
||||
categoryName = model.analyticCategoryName,
|
||||
source = model.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
activeComponent.updateState(model.uiState.value.destinationUM)
|
||||
}
|
||||
is SendFeeComponent -> {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.FeeScreenOpened(categoryName = model.analyticCategoryName),
|
||||
CommonSendAnalyticEvents.FeeScreenOpened(
|
||||
categoryName = model.analyticCategoryName,
|
||||
source = model.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -155,6 +165,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
currentRoute = model.currentRoute.filterIsInstance<CommonSendRoute.Destination>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
analyticsCategoryName = model.analyticCategoryName,
|
||||
analyticsSendSource = model.analyticsSendSource,
|
||||
title = resourceReference(R.string.common_address),
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.currency,
|
||||
|
|
@ -177,6 +188,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
callback = model,
|
||||
predefinedValues = model.predefinedValues,
|
||||
isRedesignEnabled = model.uiState.value.isRedesignEnabled,
|
||||
analyticsSendSource = model.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -204,6 +216,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
sendAmount = sendAmount,
|
||||
destinationAddress = destinationAddress,
|
||||
callback = model,
|
||||
analyticsSendSource = model.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
|
|
@ -238,6 +251,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
onSendTransaction = {
|
||||
innerRouter.replaceAll(CommonSendRoute.ConfirmSuccess)
|
||||
},
|
||||
analyticsSendSource = model.analyticsSendSource,
|
||||
),
|
||||
feeSelectorComponentFactory = feeSelectorComponentFactory,
|
||||
)
|
||||
|
|
@ -268,6 +282,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
params = SendDestinationComponentParams.DestinationBlockParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
analyticsCategoryName = model.analyticCategoryName,
|
||||
analyticsSendSource = model.analyticsSendSource,
|
||||
userWalletId = model.userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
blockClickEnableFlow = MutableStateFlow(true),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.domain.transaction.error.GetFeeError
|
|||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entity.PredefinedValues
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
|
||||
|
|
@ -50,6 +51,7 @@ internal class SendConfirmComponent(
|
|||
params = DestinationBlockParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
analyticsSendSource = params.analyticsSendSource,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrency = params.cryptoCurrencyStatus.currency,
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
|
|
@ -73,6 +75,7 @@ internal class SendConfirmComponent(
|
|||
cryptoCurrency = params.cryptoCurrencyStatus.currency,
|
||||
cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow,
|
||||
isBalanceHidingFlow = params.isBalanceHidingFlow,
|
||||
analyticsSendSource = params.analyticsSendSource,
|
||||
),
|
||||
onResult = model::onAmountResult,
|
||||
onClick = model::showEditAmount,
|
||||
|
|
@ -90,6 +93,7 @@ internal class SendConfirmComponent(
|
|||
sendAmount = model.confirmData.enteredAmount.orZero(),
|
||||
destinationAddress = model.confirmData.enteredDestination.orEmpty(),
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
analyticsSendSource = params.analyticsSendSource,
|
||||
onLoadFee = params.onLoadFee,
|
||||
),
|
||||
onResult = model::onFeeResult,
|
||||
|
|
@ -106,6 +110,7 @@ internal class SendConfirmComponent(
|
|||
feeStateConfiguration = model.feeStateConfiguration,
|
||||
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
analyticsSendSource = params.analyticsSendSource,
|
||||
),
|
||||
onResult = model::onFeeResult,
|
||||
)
|
||||
|
|
@ -165,6 +170,7 @@ internal class SendConfirmComponent(
|
|||
data class Params(
|
||||
val state: SendUM,
|
||||
val analyticsCategoryName: String,
|
||||
val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
val userWallet: UserWallet,
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
val isBalanceHiddenFlow: StateFlow<Boolean>
|
||||
field = MutableStateFlow(false)
|
||||
field = MutableStateFlow(false)
|
||||
|
||||
private val amountState
|
||||
get() = uiState.value.amountUM as? AmountState.Data
|
||||
|
|
@ -317,7 +317,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
null
|
||||
}
|
||||
|
||||
val amount = receivingAmount?.convertToSdkAmount(cryptoCurrency)
|
||||
val amount = receivingAmount?.convertToSdkAmount(cryptoCurrencyStatus)
|
||||
|
||||
saveBlockchainErrorUseCase(
|
||||
error = BlockchainErrorInfo(
|
||||
|
|
@ -331,7 +331,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
""
|
||||
},
|
||||
amount = amount?.value?.stripZeroPlainString() ?: "unknown",
|
||||
fee = feeValue?.convertToSdkAmount(cryptoCurrency)
|
||||
fee = feeValue?.convertToSdkAmount(cryptoCurrencyStatus)
|
||||
?.value?.stripZeroPlainString() ?: "unknown",
|
||||
),
|
||||
)
|
||||
|
|
@ -424,7 +424,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
|
||||
modelScope.launch {
|
||||
createTransferTransactionUseCase(
|
||||
amount = receivingAmount.convertToSdkAmount(cryptoCurrency),
|
||||
amount = receivingAmount.convertToSdkAmount(cryptoCurrencyStatus),
|
||||
fee = fee,
|
||||
memo = memo,
|
||||
nonce = nonce,
|
||||
|
|
|
|||
|
|
@ -104,12 +104,13 @@ internal class SendModel @Inject constructor(
|
|||
private val cryptoCurrency = params.currency
|
||||
|
||||
val analyticCategoryName = CommonSendAnalyticEvents.SEND_CATEGORY
|
||||
val analyticsSendSource = CommonSendAnalyticEvents.CommonSendSource.Send
|
||||
|
||||
val uiState: StateFlow<SendUM>
|
||||
field = MutableStateFlow(initialState())
|
||||
field = MutableStateFlow(initialState())
|
||||
|
||||
val isBalanceHiddenFlow: StateFlow<Boolean>
|
||||
field = MutableStateFlow(false)
|
||||
field = MutableStateFlow(false)
|
||||
|
||||
val initialRoute = if (params.amount == null) {
|
||||
if (uiState.value.isRedesignEnabled) {
|
||||
|
|
@ -260,10 +261,11 @@ internal class SendModel @Inject constructor(
|
|||
|
||||
suspend fun loadFee(): Either<GetFeeError, TransactionFee> {
|
||||
val predefinedValues = predefinedValues
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value
|
||||
val transferTransaction = if (predefinedValues is PredefinedValues.Content.Deeplink) {
|
||||
val predefinedAmount = predefinedValues.amount.parseBigDecimalOrNull()?.convertToSdkAmount(cryptoCurrency)
|
||||
val predefinedAmount = predefinedValues.amount.parseBigDecimalOrNull()
|
||||
createTransferTransactionUseCase(
|
||||
amount = predefinedAmount ?: error("Invalid amount"),
|
||||
amount = predefinedAmount?.convertToSdkAmount(cryptoCurrencyStatus) ?: error("Invalid amount"),
|
||||
memo = predefinedValues.memo,
|
||||
destination = predefinedValues.address,
|
||||
userWalletId = userWallet.walletId,
|
||||
|
|
@ -277,7 +279,7 @@ internal class SendModel @Inject constructor(
|
|||
val enteredAmount = amountUM.amountTextField.cryptoAmount.value ?: error("Invalid amount")
|
||||
|
||||
createTransferTransactionUseCase(
|
||||
amount = enteredAmount.convertToSdkAmount(cryptoCurrency),
|
||||
amount = enteredAmount.convertToSdkAmount(cryptoCurrencyStatus),
|
||||
memo = enteredMemo,
|
||||
destination = enteredDestinationAddress,
|
||||
userWalletId = userWallet.walletId,
|
||||
|
|
|
|||
|
|
@ -81,10 +81,10 @@ internal class SendConfirmSuccessModel @Inject constructor(
|
|||
iconRes = R.drawable.ic_web_24,
|
||||
onClick = ::onExploreClick,
|
||||
) to NavigationButton(
|
||||
textReference = resourceReference(R.string.common_share),
|
||||
iconRes = R.drawable.ic_share_24,
|
||||
onClick = ::onShareClick,
|
||||
)).takeIf { params.txUrl.isNotEmpty() },
|
||||
textReference = resourceReference(R.string.common_share),
|
||||
iconRes = R.drawable.ic_share_24,
|
||||
onClick = ::onShareClick,
|
||||
)).takeIf { params.txUrl.isNotEmpty() },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
|
||||
private val stackNavigation = StackNavigation<CommonSendRoute>()
|
||||
private val analyticsCategoryName = CommonSendAnalyticEvents.NFT_SEND_CATEGORY
|
||||
private val analyticsSendSource = CommonSendAnalyticEvents.CommonSendSource.NFT
|
||||
|
||||
private val innerRouter = InnerRouter<CommonSendRoute>(
|
||||
stackNavigation = stackNavigation,
|
||||
|
|
@ -83,7 +84,10 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
when (val activeComponent = stack.active.instance) {
|
||||
is NFTSendConfirmComponent -> {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.ConfirmationScreenOpened(categoryName = analyticsCategoryName),
|
||||
CommonSendAnalyticEvents.ConfirmationScreenOpened(
|
||||
categoryName = analyticsCategoryName,
|
||||
source = analyticsSendSource,
|
||||
),
|
||||
)
|
||||
if (model.currentRouteFlow.value.isEditMode) {
|
||||
activeComponent.updateState(model.uiState.value)
|
||||
|
|
@ -91,13 +95,19 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
}
|
||||
is DefaultSendDestinationComponent -> {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.AddressScreenOpened(categoryName = analyticsCategoryName),
|
||||
CommonSendAnalyticEvents.AddressScreenOpened(
|
||||
categoryName = analyticsCategoryName,
|
||||
source = analyticsSendSource,
|
||||
),
|
||||
)
|
||||
activeComponent.updateState(model.uiState.value.destinationUM)
|
||||
}
|
||||
is SendFeeComponent -> {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.FeeScreenOpened(categoryName = analyticsCategoryName),
|
||||
CommonSendAnalyticEvents.FeeScreenOpened(
|
||||
categoryName = analyticsCategoryName,
|
||||
source = analyticsSendSource,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -135,6 +145,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
title = resourceReference(R.string.nft_send),
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
analyticsSendSource = analyticsSendSource,
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = model.cryptoCurrency,
|
||||
callback = model,
|
||||
|
|
@ -159,6 +170,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
onLoadFee = model::loadFee,
|
||||
destinationAddress = destinationAddress,
|
||||
callback = model,
|
||||
analyticsSendSource = analyticsSendSource,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
|
|
@ -181,6 +193,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
currentRoute = model.currentRouteFlow.filterIsInstance<CommonSendRoute.Confirm>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
onLoadFee = model::loadFee,
|
||||
analyticsSendSource = analyticsSendSource,
|
||||
onSendTransaction = { innerRouter.replaceAll(CommonSendRoute.ConfirmSuccess) },
|
||||
),
|
||||
)
|
||||
|
|
@ -198,6 +211,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
params = NFTSendSuccessComponent.Params(
|
||||
nftSendUMFlow = model.uiState,
|
||||
analyticsCategoryName = analyticsCategoryName,
|
||||
analyticsSendSource = analyticsSendSource,
|
||||
userWallet = model.userWallet,
|
||||
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
|
||||
nftAsset = params.nftAsset,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.domain.transaction.error.GetFeeError
|
|||
import com.tangem.features.nft.component.NFTDetailsBlockComponent
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entity.PredefinedValues
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.v2.api.params.FeeSelectorParams.FeeStateConfiguration
|
||||
|
|
@ -57,6 +58,7 @@ internal class NFTSendConfirmComponent @AssistedInject constructor(
|
|||
params = DestinationBlockParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
analyticsSendSource = params.analyticsSendSource,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrency = params.cryptoCurrencyStatus.currency,
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
|
|
@ -79,6 +81,7 @@ internal class NFTSendConfirmComponent @AssistedInject constructor(
|
|||
onLoadFee = params.onLoadFee,
|
||||
destinationAddress = model.confirmData.enteredDestination.orEmpty(),
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
analyticsSendSource = params.analyticsSendSource,
|
||||
),
|
||||
onResult = model::onFeeResult,
|
||||
onClick = model::showEditFee,
|
||||
|
|
@ -94,6 +97,7 @@ internal class NFTSendConfirmComponent @AssistedInject constructor(
|
|||
feeStateConfiguration = FeeStateConfiguration.None,
|
||||
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
analyticsSendSource = params.analyticsSendSource,
|
||||
),
|
||||
onResult = model::onFeeResult,
|
||||
)
|
||||
|
|
@ -162,6 +166,7 @@ internal class NFTSendConfirmComponent @AssistedInject constructor(
|
|||
data class Params(
|
||||
val state: NFTSendUM,
|
||||
val analyticsCategoryName: String,
|
||||
val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
val userWallet: UserWallet,
|
||||
val appCurrency: AppCurrency,
|
||||
val nftAsset: NFTAsset,
|
||||
|
|
|
|||
|
|
@ -88,10 +88,10 @@ internal class NFTSendModel @Inject constructor(
|
|||
private val nftAsset = params.nftAsset
|
||||
|
||||
val uiState: StateFlow<NFTSendUM>
|
||||
field = MutableStateFlow(initialState())
|
||||
field = MutableStateFlow(initialState())
|
||||
|
||||
val isBalanceHiddenFlow: StateFlow<Boolean>
|
||||
field = MutableStateFlow(false)
|
||||
field = MutableStateFlow(false)
|
||||
|
||||
var cryptoCurrency: CryptoCurrency by Delegates.notNull()
|
||||
var userWallet: UserWallet by Delegates.notNull()
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.features.nft.component.NFTDetailsBlockComponent
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entity.PredefinedValues
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
|
||||
|
|
@ -54,6 +55,7 @@ internal class NFTSendSuccessComponent @AssistedInject constructor(
|
|||
params = DestinationBlockParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
analyticsSendSource = params.analyticsSendSource,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrency = params.cryptoCurrencyStatus.currency,
|
||||
blockClickEnableFlow = MutableStateFlow(false),
|
||||
|
|
@ -77,6 +79,7 @@ internal class NFTSendSuccessComponent @AssistedInject constructor(
|
|||
data class Params(
|
||||
val nftSendUMFlow: StateFlow<NFTSendUM>,
|
||||
val analyticsCategoryName: String,
|
||||
val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
val currentRoute: Flow<CommonSendRoute>,
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val userWallet: UserWallet,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.entity.PredefinedValues
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponent.ModelCallback
|
||||
|
|
@ -15,6 +16,7 @@ internal sealed class SendAmountComponentParams {
|
|||
|
||||
abstract val state: AmountState
|
||||
abstract val analyticsCategoryName: String
|
||||
abstract val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource
|
||||
abstract val userWalletId: UserWalletId
|
||||
abstract val appCurrency: AppCurrency
|
||||
abstract val predefinedValues: PredefinedValues
|
||||
|
|
@ -33,6 +35,7 @@ internal sealed class SendAmountComponentParams {
|
|||
override val cryptoCurrency: CryptoCurrency,
|
||||
override val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
|
||||
override val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
val callback: ModelCallback,
|
||||
val currentRoute: StateFlow<CommonSendRoute>,
|
||||
) : SendAmountComponentParams()
|
||||
|
|
@ -47,6 +50,7 @@ internal sealed class SendAmountComponentParams {
|
|||
override val cryptoCurrency: CryptoCurrency,
|
||||
override val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
|
||||
override val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
val userWallet: UserWallet,
|
||||
val blockClickEnableFlow: StateFlow<Boolean>,
|
||||
) : SendAmountComponentParams()
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ internal class SendAmountModel @Inject constructor(
|
|||
|
||||
private var isAvailableForSwap: Boolean = false
|
||||
val isSendWithSwapAvailable: StateFlow<Boolean>
|
||||
field = MutableStateFlow(false)
|
||||
field = MutableStateFlow(false)
|
||||
|
||||
private val analyticsCategoryName = params.analyticsCategoryName
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
|
||||
|
|
@ -246,6 +246,7 @@ internal class SendAmountModel @Inject constructor(
|
|||
categoryName = analyticsCategoryName,
|
||||
token = params.cryptoCurrency.symbol,
|
||||
blockchain = params.cryptoCurrency.network.name,
|
||||
source = params.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.send.v2.subcomponents.destination.analytics
|
|||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.VALIDATION
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
|
||||
internal sealed class SendDestinationAnalyticEvents(
|
||||
category: String,
|
||||
|
|
@ -15,13 +16,15 @@ internal sealed class SendDestinationAnalyticEvents(
|
|||
/** Address to send entered */
|
||||
data class AddressEntered(
|
||||
override val categoryName: String,
|
||||
val source: EnterAddressSource,
|
||||
val method: EnterAddressSource,
|
||||
val isValid: Boolean,
|
||||
val source: CommonSendAnalyticEvents.CommonSendSource,
|
||||
) : SendDestinationAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Address Entered",
|
||||
params = mapOf(
|
||||
SOURCE to source.name,
|
||||
SOURCE to source.analyticsName,
|
||||
"Method" to method.name,
|
||||
VALIDATION to if (isValid) "Success" else "Fail",
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -275,7 +275,8 @@ internal class SendDestinationModel @Inject constructor(
|
|||
analyticsEventHandler.send(
|
||||
SendDestinationAnalyticEvents.AddressEntered(
|
||||
categoryName = analyticsCategoryName,
|
||||
source = it,
|
||||
source = params.analyticsSendSource,
|
||||
method = it,
|
||||
isValid = addressValidationResult.isRight(),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.common.CommonSendRoute
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -23,6 +24,7 @@ internal sealed class SendFeeComponentParams {
|
|||
abstract val sendAmount: BigDecimal
|
||||
abstract val destinationAddress: String
|
||||
abstract val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>
|
||||
abstract val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource
|
||||
|
||||
data class FeeParams(
|
||||
override val state: FeeUM,
|
||||
|
|
@ -34,6 +36,7 @@ internal sealed class SendFeeComponentParams {
|
|||
override val sendAmount: BigDecimal,
|
||||
override val destinationAddress: String,
|
||||
override val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>,
|
||||
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
val currentRoute: Flow<CommonSendRoute.Fee>,
|
||||
val callback: SendFeeComponent.ModelCallback,
|
||||
) : SendFeeComponentParams()
|
||||
|
|
@ -48,6 +51,7 @@ internal sealed class SendFeeComponentParams {
|
|||
override val sendAmount: BigDecimal,
|
||||
override val destinationAddress: String,
|
||||
override val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>,
|
||||
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
|
||||
val blockClickEnableFlow: StateFlow<Boolean>,
|
||||
) : SendFeeComponentParams()
|
||||
}
|
||||
|
|
@ -22,13 +22,13 @@ import com.tangem.features.send.v2.subcomponents.fee.model.transformers.*
|
|||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType
|
||||
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
|
||||
import com.tangem.utils.TangemLinks
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -136,13 +136,7 @@ internal class SendFeeModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onReadMoreClick() {
|
||||
val locale = if (Locale.getDefault().language == RU_LOCALE) RU_LOCALE else EN_LOCALE
|
||||
val url = buildString {
|
||||
append(FEE_READ_MORE_URL_FIRST_PART)
|
||||
append(locale)
|
||||
append(FEE_READ_MORE_URL_SECOND_PART)
|
||||
}
|
||||
urlOpener.openUrl(url)
|
||||
urlOpener.openUrl(TangemLinks.FEE_BLOG_LINK)
|
||||
}
|
||||
|
||||
override fun onNextClick() {
|
||||
|
|
@ -162,6 +156,7 @@ internal class SendFeeModel @Inject constructor(
|
|||
CommonSendFeeAnalyticEvents.SelectedFee(
|
||||
categoryName = analyticsCategoryName,
|
||||
feeType = feeSelectorUM.selectedType.toAnalyticType(feeSelectorUM),
|
||||
source = params.analyticsSendSource,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -297,11 +292,4 @@ internal class SendFeeModel @Inject constructor(
|
|||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val RU_LOCALE = "ru"
|
||||
const val EN_LOCALE = "en"
|
||||
const val FEE_READ_MORE_URL_FIRST_PART = "https://tangem.com/"
|
||||
const val FEE_READ_MORE_URL_SECOND_PART = "/blog/post/what-is-a-transaction-fee-and-why-do-we-need-it/"
|
||||
}
|
||||
}
|
||||
|
|
@ -287,7 +287,7 @@ internal class NotificationsModel @Inject constructor(
|
|||
) {
|
||||
val validationError = validateTransactionUseCase(
|
||||
userWalletId = userWalletId,
|
||||
amount = enteredAmount.convertToSdkAmount(currency),
|
||||
amount = enteredAmount.convertToSdkAmount(cryptoCurrencyStatus),
|
||||
fee = fee,
|
||||
memo = memo,
|
||||
destination = destinationAddress,
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ internal class StakingModel @Inject constructor(
|
|||
isInitialInfoStep && noBalanceState && !isAccountInitialized -> {
|
||||
analyticsEventHandler.send(StakingAnalyticsEvent.UnitializedAddress(
|
||||
token = cryptoCurrencyStatus.currency.symbol,
|
||||
),)
|
||||
))
|
||||
stakingEventFactory.createInitializeAccountAlert()
|
||||
return@launch
|
||||
}
|
||||
|
|
@ -662,7 +662,7 @@ internal class StakingModel @Inject constructor(
|
|||
contractAddress = tokenCryptoCurrency.contractAddress,
|
||||
spenderAddress = approval.spenderAddress,
|
||||
fee = fee,
|
||||
cryptoCurrency = tokenCryptoCurrency,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
userWalletId = userWalletId,
|
||||
).fold(
|
||||
ifLeft = { error ->
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue