Updated on 2026-08-14
This commit is contained in:
commit
4076059447
1222 changed files with 26275 additions and 12629 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,35 @@
|
|||
package com.tangem.features.account
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
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.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
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 : ComponentFactory<Params, PortfolioSelectorComponent>
|
||||
}
|
||||
|
||||
/**
|
||||
* if [isAccountMode] is false it's mean [selectedAccount] emit [AccountId] for Main account
|
||||
*/
|
||||
interface PortfolioSelectorController {
|
||||
val isAccountMode: Flow<Boolean>
|
||||
val selectedAccount: StateFlow<AccountId?>
|
||||
|
||||
fun selectAccount(accountId: AccountId?)
|
||||
fun selectedAccountWithData(portfolioFetcher: PortfolioFetcher): Flow<Pair<UserWallet, AccountStatus>?>
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.features.account.createedit
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.common.ui.account.AccountNameUM
|
||||
import com.tangem.common.ui.account.toDomain
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
|
|
@ -13,11 +13,11 @@ import com.tangem.core.decompose.navigation.Router
|
|||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.res.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.ToastMessage
|
||||
import com.tangem.core.ui.utils.showErrorDialog
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase
|
||||
import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase
|
||||
|
|
@ -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) {
|
||||
|
|
@ -75,13 +75,17 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
)
|
||||
val firstAction = EventMessageAction(
|
||||
title = resourceReference(R.string.account_unsaved_dialog_action_second),
|
||||
warning = true,
|
||||
isWarning = true,
|
||||
onClick = { router.pop() },
|
||||
)
|
||||
val messageRes = when (params) {
|
||||
is AccountCreateEditComponent.Params.Create -> R.string.account_unsaved_dialog_message_create
|
||||
is AccountCreateEditComponent.Params.Edit -> R.string.account_unsaved_dialog_message_edit
|
||||
}
|
||||
messageSender.send(
|
||||
DialogMessage(
|
||||
title = resourceReference(R.string.account_unsaved_dialog_title),
|
||||
message = resourceReference(R.string.account_unsaved_dialog_message_create),
|
||||
message = resourceReference(messageRes),
|
||||
firstActionBuilder = { firstAction },
|
||||
secondActionBuilder = { secondAction },
|
||||
),
|
||||
|
|
@ -110,20 +114,34 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
derivationIndex = derivationIndex,
|
||||
)
|
||||
uiState.value = uiState.value.toggleProgress(showProgress = false)
|
||||
|
||||
result
|
||||
.onLeft { showMessage(it.toString()) }
|
||||
.onLeft(::handleAddAccountError)
|
||||
.onRight {
|
||||
showMessage(R.string.account_create_success_message)
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAddAccountError(error: AddCryptoPortfolioUseCase.Error) {
|
||||
val duplicateAccountNames = (error as? AddCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet)
|
||||
?.cause is AccountList.Error.DuplicateAccountNames
|
||||
when {
|
||||
duplicateAccountNames -> showAccountNameExist()
|
||||
else -> {
|
||||
showSomethingWrong()
|
||||
logError(error = AccountFeatureError.CreateAccount.FailedToCreateAccount(cause = error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun editCryptoPortfolio(params: AccountCreateEditComponent.Params.Edit) {
|
||||
val state = uiState.value
|
||||
val name = state.account.name.toDomain().getOrNull() ?: return
|
||||
val icon = state.account.portfolioIcon.toDomain()
|
||||
val isNewName = name != params.account.accountName
|
||||
val isNewIcon = icon != params.account.portfolioIcon
|
||||
|
||||
uiState.value = uiState.value.toggleProgress(showProgress = true)
|
||||
val result = updateCryptoPortfolioUseCase(
|
||||
icon = if (isNewIcon) icon else null,
|
||||
|
|
@ -131,25 +149,37 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
accountId = params.account.accountId,
|
||||
)
|
||||
uiState.value = uiState.value.toggleProgress(showProgress = false)
|
||||
|
||||
result
|
||||
.onLeft { showMessage(it.toString()) }
|
||||
.onLeft(::handleEditAccountError)
|
||||
.onRight {
|
||||
showMessage(R.string.account_edit_success_message)
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleEditAccountError(error: UpdateCryptoPortfolioUseCase.Error) {
|
||||
val duplicateAccountNames = (error as? UpdateCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet)
|
||||
?.cause is AccountList.Error.DuplicateAccountNames
|
||||
when {
|
||||
duplicateAccountNames -> showAccountNameExist()
|
||||
else -> {
|
||||
showSomethingWrong()
|
||||
logError(error = AccountFeatureError.EditAccount.FailedToEditAccount(cause = error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showMessage(@StringRes id: Int) {
|
||||
val message = resourceReference(id)
|
||||
messageSender.send(ToastMessage(message = message))
|
||||
}
|
||||
|
||||
private fun showMessage(text: String) {
|
||||
messageSender.send(ToastMessage(message = stringReference(text)))
|
||||
private fun onCloseClick() {
|
||||
val showConfirmDialog = uiState.value.buttonState.isButtonEnabled
|
||||
if (showConfirmDialog) unsaveChangeDialog() else router.pop()
|
||||
}
|
||||
|
||||
private fun onCloseClick() = unsaveChangeDialog()
|
||||
|
||||
private fun onIconSelect(icon: CryptoPortfolioIcon.Icon) {
|
||||
uiState.value = uiState.value
|
||||
.updateIconSelect(icon)
|
||||
|
|
@ -203,33 +233,46 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
.onLeft { cause ->
|
||||
handleError(
|
||||
error = AccountFeatureError.CreateAccount.UnableToGetDerivationIndex,
|
||||
message = cause.toString(),
|
||||
val error = AccountFeatureError.CreateAccount.UnableToGetDerivationIndex(cause)
|
||||
|
||||
logError(
|
||||
error = error,
|
||||
params = mapOf(
|
||||
"userWalletId" to userWalletId.stringValue,
|
||||
"cause" to cause.toString(),
|
||||
),
|
||||
)
|
||||
|
||||
messageSender.showErrorDialog(universalError = error, onDismiss = router::pop)
|
||||
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleError(
|
||||
error: AccountFeatureError,
|
||||
message: String? = null,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) {
|
||||
val exception = IllegalStateException("$error. Cause: $message")
|
||||
private fun logError(error: AccountFeatureError, params: Map<String, String> = mapOf()) {
|
||||
val exception = IllegalStateException(error.toString())
|
||||
|
||||
Timber.e(exception)
|
||||
|
||||
analyticsExceptionHandler.sendException(
|
||||
event = ExceptionAnalyticsEvent(exception = exception, params = params),
|
||||
)
|
||||
}
|
||||
|
||||
messageSender.showErrorDialog(universalError = error, onDismiss = router::pop)
|
||||
private fun showSomethingWrong() {
|
||||
val dialogMessage = DialogMessage(
|
||||
title = resourceReference(R.string.common_something_went_wrong),
|
||||
message = resourceReference(R.string.account_could_not_create),
|
||||
)
|
||||
messageSender.send(dialogMessage)
|
||||
}
|
||||
|
||||
private fun showAccountNameExist() {
|
||||
val dialogMessage = DialogMessage(
|
||||
title = resourceReference(R.string.common_something_went_wrong),
|
||||
message = resourceReference(R.string.account_form_name_already_exist_error_description),
|
||||
)
|
||||
messageSender.send(dialogMessage)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.features.account.createedit.error
|
||||
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase
|
||||
import com.tangem.domain.account.usecase.RecoverCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase
|
||||
|
||||
sealed interface AccountFeatureError : UniversalError {
|
||||
|
||||
|
|
@ -14,16 +18,29 @@ sealed interface AccountFeatureError : UniversalError {
|
|||
|
||||
override val subsystemCode: String get() = "001"
|
||||
|
||||
data object UnableToGetDerivationIndex : CreateAccount {
|
||||
data class UnableToGetDerivationIndex(val cause: GetUnoccupiedAccountIndexUseCase.Error) : CreateAccount {
|
||||
override val specificErrorCode: String = "001"
|
||||
}
|
||||
|
||||
data class FailedToCreateAccount(val cause: AddCryptoPortfolioUseCase.Error) : CreateAccount {
|
||||
override val specificErrorCode: String = "002"
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface EditAccount : AccountFeatureError {
|
||||
|
||||
override val subsystemCode: String get() = "002"
|
||||
|
||||
data object RequiredCryptoPortfolio : EditAccount {
|
||||
data class FailedToEditAccount(val cause: UpdateCryptoPortfolioUseCase.Error) : EditAccount {
|
||||
override val specificErrorCode: String = "001"
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface ArchivedAccountList : AccountFeatureError {
|
||||
|
||||
override val subsystemCode: String get() = "003"
|
||||
|
||||
data class FailedToRecoverAccount(val cause: RecoverCryptoPortfolioUseCase.Error) : ArchivedAccountList {
|
||||
override val specificErrorCode: String = "001"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -30,8 +32,8 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.fields.AutoSizeTextField
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -43,9 +45,11 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
|
||||
@Composable
|
||||
internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modifier = Modifier) {
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(color = TangemTheme.colors.background.tertiary)
|
||||
.background(color = TangemTheme.colors.background.secondary)
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.systemBarsPadding(),
|
||||
|
|
@ -76,6 +80,10 @@ internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modi
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
if (state.buttonState.showProgress) {
|
||||
focusManager.clearFocus()
|
||||
keyboardController?.hide()
|
||||
}
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -161,6 +169,7 @@ private fun AccountColor(colorsState: AccountCreateEditUM.Colors) {
|
|||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = { colorsState.onColorSelect(color) })
|
||||
.size(48.dp),
|
||||
) {
|
||||
|
|
@ -208,6 +217,7 @@ private fun AccountIcons(iconsState: AccountCreateEditUM.Icons) {
|
|||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = { iconsState.onIconSelect(icon) })
|
||||
.size(52.dp),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
)
|
||||
val firstAction = EventMessageAction(
|
||||
title = resourceReference(R.string.account_details_archive_action),
|
||||
warning = true,
|
||||
isWarning = true,
|
||||
onClick = ::archiveCryptoPortfolio,
|
||||
)
|
||||
messageSender.send(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.features.account.di
|
||||
|
||||
import com.tangem.features.account.PortfolioFetcher
|
||||
import com.tangem.features.account.PortfolioSelectorComponent
|
||||
import com.tangem.features.account.PortfolioSelectorController
|
||||
import com.tangem.features.account.fetcher.DefaultPortfolioFetcher
|
||||
import com.tangem.features.account.selector.DefaultPortfolioSelectorComponent
|
||||
import com.tangem.features.account.selector.DefaultPortfolioSelectorController
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface AccountFeatureModule {
|
||||
|
||||
@Binds
|
||||
fun bindPortfolioFetcherFactory(impl: DefaultPortfolioFetcher.Factory): PortfolioFetcher.Factory
|
||||
|
||||
@Binds
|
||||
fun bindPortfolioSelectorController(impl: DefaultPortfolioSelectorController): PortfolioSelectorController
|
||||
|
||||
@Binds
|
||||
fun bindPortfolioSelectorComponentFactory(
|
||||
impl: DefaultPortfolioSelectorComponent.Factory,
|
||||
): PortfolioSelectorComponent.Factory
|
||||
}
|
||||
|
|
@ -1,17 +1,16 @@
|
|||
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 +19,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>(
|
||||
|
|
@ -56,8 +58,8 @@ internal class DefaultAccountsBalanceFetcher @AssistedInject constructor(
|
|||
.map { it.filterWallets(mode) }
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { wallets -> balancesForWallets(wallets) },
|
||||
flow2 = appCurrencyFlow(),
|
||||
flow3 = balanceHidingFlow(),
|
||||
flow2 = getSelectedAppCurrencyUseCase.invokeOrDefault(),
|
||||
flow3 = getBalanceHidingSettingsUseCase.isBalanceHidden(),
|
||||
) { balances, appCurrency, isBalanceHiding ->
|
||||
Data(
|
||||
appCurrency = appCurrency,
|
||||
|
|
@ -73,48 +75,25 @@ 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 appCurrencyFlow(): Flow<AppCurrency> {
|
||||
return getSelectedAppCurrencyUseCase()
|
||||
.map { it.getOrElse { AppCurrency.Default } }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun balanceHidingFlow(): Flow<Boolean> {
|
||||
return getBalanceHidingSettingsUseCase()
|
||||
.map { it.isBalanceHidden }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
private fun accountStatusListFlow(wallet: UserWallet): Flow<AccountStatusList> =
|
||||
singleAccountStatusListSupplier(SingleAccountStatusListProducer.Params(wallet.walletId))
|
||||
|
||||
@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.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
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, AccountStatus>?> =
|
||||
combine(
|
||||
flow = _selectedAccount,
|
||||
flow2 = portfolioFetcher.data,
|
||||
transform = { accountId, data ->
|
||||
accountId ?: return@combine null
|
||||
var result: Pair<UserWallet, AccountStatus>? = null
|
||||
|
||||
data.balances.forEach { wallet, balance ->
|
||||
val accountStatuses = balance.accountsBalance.accountStatuses
|
||||
.find { accountId == it.account.accountId }
|
||||
if (accountStatuses != null) result = wallet to accountStatuses
|
||||
}
|
||||
|
||||
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)
|
||||
},
|
||||
)
|
||||
|
|
@ -7,7 +7,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
interface AskBiometryComponent : ComposableContentComponent, ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(
|
||||
val bottomSheetVariant: Boolean,
|
||||
val isBottomSheetVariant: Boolean,
|
||||
val modelCallbacks: ModelCallbacks,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -55,7 +55,7 @@ internal class AskBiometryModel @Inject constructor(
|
|||
|
||||
private val _uiState = MutableStateFlow(
|
||||
AskBiometryUM(
|
||||
bottomSheetVariant = params.bottomSheetVariant,
|
||||
bottomSheetVariant = params.isBottomSheetVariant,
|
||||
onAllowClick = ::onAllowClick,
|
||||
onDontAllowClick = ::dontAllow,
|
||||
onDismiss = ::dismiss,
|
||||
|
|
@ -160,7 +160,7 @@ internal class AskBiometryModel @Inject constructor(
|
|||
title = resourceReference(R.string.common_cancel),
|
||||
onClick = {},
|
||||
),
|
||||
dismissOnFirstAction = true,
|
||||
shouldDismissOnFirstAction = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ dependencies {
|
|||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.common.ui)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.models)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.disclaimer.impl.model
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.ui.notifications.NotificationId
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -49,6 +50,10 @@ internal class DisclaimerModel @Inject constructor(
|
|||
cardRepository.acceptTangemTOS()
|
||||
val shouldAskPushPermission = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
|
||||
if (shouldAskPushPermission) {
|
||||
notificationsRepository.setShouldShowNotifications(
|
||||
key = NotificationId.EnablePushesReminderNotification.key,
|
||||
value = false,
|
||||
)
|
||||
router.push(AppRoute.PushNotification(AppRoute.PushNotification.Source.Stories))
|
||||
} else {
|
||||
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.settings.ShouldAskPermissionUseCase
|
||||
import com.tangem.features.hotwallet.addexistingwallet.entry.routing.AddExistingWalletRoute
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent
|
||||
import com.tangem.features.hotwallet.accesscode.AccessCodeComponent
|
||||
import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent
|
||||
|
|
@ -36,7 +35,6 @@ internal class AddExistingWalletModel @Inject constructor(
|
|||
) : Model() {
|
||||
|
||||
val hotWalletStepperComponentModelCallback = HotWalletStepperComponentModelCallback()
|
||||
val addExistingWalletStartModelCallbacks = AddExistingWalletStartModelCallbacks()
|
||||
val addExistingWalletImportModelCallbacks = AddExistingWalletImportModelCallbacks()
|
||||
val manualBackupCompletedComponentModelCallbacks = ManualBackupCompletedComponentModelCallbacks()
|
||||
val accessCodeModelCallbacks = AccessCodeModelCallbacks()
|
||||
|
|
@ -44,13 +42,12 @@ internal class AddExistingWalletModel @Inject constructor(
|
|||
val mobileWalletSetupFinishedComponentModelCallbacks = MobileWalletSetupFinishedComponentModelCallbacks()
|
||||
|
||||
val stackNavigation = StackNavigation<AddExistingWalletRoute>()
|
||||
val startRoute = AddExistingWalletRoute.Start
|
||||
val startRoute = AddExistingWalletRoute.Import
|
||||
val currentRoute: MutableStateFlow<AddExistingWalletRoute> = MutableStateFlow(startRoute)
|
||||
|
||||
fun onChildBack() {
|
||||
when (currentRoute.value) {
|
||||
is AddExistingWalletRoute.Start -> router.pop()
|
||||
is AddExistingWalletRoute.Import -> stackNavigation.pop()
|
||||
is AddExistingWalletRoute.Import -> router.pop()
|
||||
is AddExistingWalletRoute.BackupCompleted -> Unit
|
||||
is AddExistingWalletRoute.SetAccessCode -> Unit
|
||||
is AddExistingWalletRoute.ConfirmAccessCode -> stackNavigation.pop()
|
||||
|
|
@ -87,7 +84,7 @@ internal class AddExistingWalletModel @Inject constructor(
|
|||
title = resourceReference(R.string.access_code_alert_skip_ok),
|
||||
onClick = { navigateToPushNotificationsOrNext() },
|
||||
),
|
||||
dismissOnFirstAction = true,
|
||||
shouldDismissOnFirstAction = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -102,16 +99,6 @@ internal class AddExistingWalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
inner class AddExistingWalletStartModelCallbacks : AddExistingWalletStartComponent.ModelCallbacks {
|
||||
override fun onBackClick() {
|
||||
router.pop()
|
||||
}
|
||||
|
||||
override fun onImportPhraseClick() {
|
||||
stackNavigation.push(AddExistingWalletRoute.Import)
|
||||
}
|
||||
}
|
||||
|
||||
inner class AddExistingWalletImportModelCallbacks : AddExistingWalletImportComponent.ModelCallbacks {
|
||||
override fun onWalletImported(userWalletId: UserWalletId) {
|
||||
stackNavigation.replaceAll(AddExistingWalletRoute.BackupCompleted(userWalletId))
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ internal class AddExistingWalletStepperStateManager {
|
|||
|
||||
fun getStepperState(route: AddExistingWalletRoute): HotWalletStepperComponent.StepperUM? {
|
||||
return when (route) {
|
||||
is AddExistingWalletRoute.Start -> null
|
||||
|
||||
is AddExistingWalletRoute.Import -> HotWalletStepperComponent.StepperUM(
|
||||
currentStep = STEP_IMPORT,
|
||||
steps = STEPS_COUNT,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import com.tangem.core.decompose.context.AppComponentContext
|
|||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.entry.AddExistingWalletModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent
|
||||
import com.tangem.features.hotwallet.accesscode.AccessCodeComponent
|
||||
import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent
|
||||
|
|
@ -24,12 +23,6 @@ internal class AddExistingWalletChildFactory @Inject constructor(
|
|||
model: AddExistingWalletModel,
|
||||
): ComposableContentComponent {
|
||||
return when (route) {
|
||||
is AddExistingWalletRoute.Start -> AddExistingWalletStartComponent(
|
||||
context = childContext,
|
||||
params = AddExistingWalletStartComponent.Params(
|
||||
callbacks = model.addExistingWalletStartModelCallbacks,
|
||||
),
|
||||
)
|
||||
is AddExistingWalletRoute.Import -> AddExistingWalletImportComponent(
|
||||
context = childContext,
|
||||
params = AddExistingWalletImportComponent.Params(
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
internal sealed class AddExistingWalletRoute : Route {
|
||||
|
||||
@Serializable
|
||||
object Start : AddExistingWalletRoute()
|
||||
|
||||
@Serializable
|
||||
object Import : AddExistingWalletRoute()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start
|
||||
|
||||
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.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.ui.AddExistingWalletStartContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class AddExistingWalletStartComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
private val model: AddExistingWalletStartModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
AddExistingWalletStartContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelCallbacks {
|
||||
fun onBackClick()
|
||||
fun onImportPhraseClick()
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val callbacks: ModelCallbacks,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic.SignedIn
|
||||
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
|
||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
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.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.analytics.IntroductionProcess
|
||||
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.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.entity.AddExistingWalletStartUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val HIDE_PROGRESS_DELAY = 400L
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class AddExistingWalletStartModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val appRouter: AppRouter,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
|
||||
) : Model() {
|
||||
|
||||
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,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
showWantToPurchaseBlockWithDelay()
|
||||
}
|
||||
|
||||
private fun showWantToPurchaseBlockWithDelay() {
|
||||
modelScope.launch {
|
||||
delay(SHOW_WANT_TO_PURCHASE_BLOCK_DELAY)
|
||||
uiState.update { it.copy(showWantToPurchaseBlock = true) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onShopClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards)
|
||||
analyticsEventHandler.send(Shop.ScreenOpened)
|
||||
modelScope.launch {
|
||||
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onScanClick() {
|
||||
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard)
|
||||
scanCard()
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
modelScope.launch {
|
||||
setLoading(true)
|
||||
|
||||
val shouldSaveAccessCodes = settingsRepository.shouldSaveAccessCodes()
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
val analyticsSource = AnalyticsParam.ScreensSources.Intro
|
||||
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = analyticsSource,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (!showProgress) {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
} else {
|
||||
setLoading(true)
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
handleScanError(error)
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
proceedWithScanResponse(scanResponse)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
|
||||
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build()
|
||||
|
||||
if (userWallet == null) {
|
||||
Timber.e("User wallet not created")
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
saveWalletUseCase(userWallet).fold(
|
||||
ifLeft = {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
when (it) {
|
||||
is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet")
|
||||
is SaveWalletError.WalletAlreadySaved -> {
|
||||
userWalletsListRepository.unlock(
|
||||
userWalletId = userWallet.walletId,
|
||||
unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse),
|
||||
).onRight {
|
||||
appRouter.replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ifRight = {
|
||||
setLoading(false)
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
appRouter.replaceAll(AppRoute.Wallet)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
||||
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
|
||||
if (currency != null) {
|
||||
analyticsEventHandler.send(
|
||||
SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = SignInType.Card,
|
||||
walletsCount = userWalletsListRepository.userWalletsSync().size.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLoading(isLoading: Boolean) {
|
||||
uiState.update { it.copy(isScanInProgress = isLoading) }
|
||||
}
|
||||
|
||||
fun handleScanError(error: TangemError) {
|
||||
when (error) {
|
||||
is TangemSdkError.NfcFeatureIsUnavailable -> handleNfcFeatureUnavailable()
|
||||
is TangemSdkError -> Timber.e(error, "Scan error occurred")
|
||||
else -> Timber.e(error, "Error happened")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNfcFeatureUnavailable() {
|
||||
uiMessageSender.send(
|
||||
message = DialogMessage(
|
||||
message = resourceReference(R.string.nfc_error_unavailable),
|
||||
title = resourceReference(id = R.string.common_error),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SHOW_WANT_TO_PURCHASE_BLOCK_DELAY = 3000L
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface AddExistingWalletStartModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(AddExistingWalletStartModel::class)
|
||||
fun bindAddExistingWalletStartModel(model: AddExistingWalletStartModel): Model
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start.entity
|
||||
|
||||
internal data class AddExistingWalletStartUM(
|
||||
val showWantToPurchaseBlock: Boolean,
|
||||
val isScanInProgress: Boolean,
|
||||
val onBackClick: () -> Unit,
|
||||
val onImportPhraseClick: () -> Unit,
|
||||
val onScanCardClick: () -> Unit,
|
||||
val onBuyCardClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.start.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.entity.AddExistingWalletStartUM
|
||||
import com.tangem.features.hotwallet.common.ui.OptionBlock
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun AddExistingWalletStartContent(state: AddExistingWalletStartUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding(),
|
||||
startButton = TopAppBarButtonUM.Back(state.onBackClick),
|
||||
title = "Add existing wallet",
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 24.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = stringResourceSafe(R.string.wallet_import_seed_navtitle),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
OptionBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = 32.dp),
|
||||
backgroundColor = TangemTheme.colors.background.secondary,
|
||||
title = stringResourceSafe(R.string.wallet_import_seed_title),
|
||||
description = stringResourceSafe(R.string.wallet_import_seed_description),
|
||||
badge = null,
|
||||
onClick = state.onImportPhraseClick,
|
||||
enabled = true,
|
||||
)
|
||||
OptionBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp),
|
||||
backgroundColor = TangemTheme.colors.background.secondary,
|
||||
title = stringResourceSafe(R.string.wallet_import_scan_title),
|
||||
description = stringResourceSafe(R.string.wallet_import_scan_description),
|
||||
badge = {
|
||||
if (state.isScanInProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(20.dp)
|
||||
.padding(2.dp),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
strokeWidth = TangemTheme.dimens.size2,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(20.dp),
|
||||
painter = painterResource(R.drawable.ic_tangem_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = state.onScanCardClick,
|
||||
enabled = true,
|
||||
)
|
||||
OptionBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp),
|
||||
backgroundColor = TangemTheme.colors.background.secondary,
|
||||
title = stringResourceSafe(R.string.wallet_import_google_drive_title),
|
||||
description = stringResourceSafe(R.string.wallet_import_google_drive_description),
|
||||
badge = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors.field.focused,
|
||||
shape = TangemTheme.shapes.roundedCorners8,
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.common_coming_soon),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = null,
|
||||
enabled = false,
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(state.showWantToPurchaseBlock) {
|
||||
BuyTangemWalletBlock(
|
||||
onScanClick = state.onBuyCardClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BuyTangemWalletBlock(onScanClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors.field.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(
|
||||
horizontal = 20.dp,
|
||||
vertical = 16.dp,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 16.dp),
|
||||
text = stringResourceSafe(R.string.wallet_import_buy_question),
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth(),
|
||||
text = stringResourceSafe(R.string.wallet_import_buy_title),
|
||||
onClick = onScanClick,
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
enabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewCreateWalletContent() {
|
||||
TangemThemePreview {
|
||||
AddExistingWalletStartContent(
|
||||
state = AddExistingWalletStartUM(
|
||||
showWantToPurchaseBlock = true,
|
||||
isScanInProgress = true,
|
||||
onBackClick = {},
|
||||
onImportPhraseClick = {},
|
||||
onScanCardClick = {},
|
||||
onBuyCardClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -28,13 +28,18 @@ 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() },
|
||||
onImportClick = ::onImportClick,
|
||||
onCreateClick = ::onCreateClick,
|
||||
createButtonLoading = false,
|
||||
),
|
||||
)
|
||||
|
||||
private fun onImportClick() {
|
||||
router.push(AppRoute.AddExistingWallet)
|
||||
}
|
||||
|
||||
private fun onCreateClick() {
|
||||
modelScope.launch {
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ package com.tangem.features.hotwallet.createmobilewallet.entity
|
|||
internal data class CreateMobileWalletUM(
|
||||
val createButtonLoading: Boolean,
|
||||
val onBackClick: () -> Unit,
|
||||
val onImportClick: () -> Unit,
|
||||
val onCreateClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -80,13 +81,23 @@ internal fun CreateMobileWalletContent(state: CreateMobileWalletUM, modifier: Mo
|
|||
iconRes = R.drawable.ic_settings_24,
|
||||
)
|
||||
}
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
text = stringResourceSafe(R.string.hw_import_existing_wallet),
|
||||
onClick = state.onImportClick,
|
||||
)
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
text = stringResourceSafe(R.string.common_create),
|
||||
text = stringResourceSafe(R.string.onboarding_create_wallet_button_create_wallet),
|
||||
showProgress = state.createButtonLoading,
|
||||
enabled = true,
|
||||
onClick = state.onCreateClick,
|
||||
)
|
||||
}
|
||||
|
|
@ -134,6 +145,7 @@ private fun PreviewCreateWalletContent() {
|
|||
state = CreateMobileWalletUM(
|
||||
onBackClick = {},
|
||||
createButtonLoading = false,
|
||||
onImportClick = {},
|
||||
onCreateClick = {},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ internal class WalletActivationModel @Inject constructor(
|
|||
title = resourceReference(R.string.access_code_alert_skip_ok),
|
||||
onClick = { navigateToPushNotificationsOrNext() },
|
||||
),
|
||||
dismissOnFirstAction = true,
|
||||
shouldDismissOnFirstAction = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,19 @@ 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
|
||||
*/
|
||||
@Suppress("UnusedPrivateProperty")
|
||||
internal class MockKycComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: KycComponent.Params,
|
||||
) : 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
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ interface ChooseManagedTokensComponent : ComposableContentComponent {
|
|||
val initialCurrency: CryptoCurrency,
|
||||
val selectedCurrency: CryptoCurrency?,
|
||||
val source: Source,
|
||||
val showSendViaSwapNotification: Boolean,
|
||||
val shouldShowSendViaSwapNotification: Boolean,
|
||||
val callback: ModelCallback? = null,
|
||||
val analyticsCategoryName: String,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
|||
sealed class CommonManageTokensAnalyticEvents(
|
||||
category: String,
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent(category = category, event = event, params = params) {
|
||||
|
||||
data class TokenSearchClicked(
|
||||
|
|
@ -26,8 +26,8 @@ sealed class CommonManageTokensAnalyticEvents(
|
|||
event = "Token Searched",
|
||||
params = buildMap {
|
||||
put(CHOSEN_TOKEN, if (isTokenChosen) "Yes" else "No")
|
||||
token?.let { put(TOKEN_PARAM, token) }
|
||||
blockchain?.let { put(BLOCKCHAIN, blockchain) }
|
||||
if (token != null) { put(TOKEN_PARAM, token) }
|
||||
if (blockchain != null) { put(BLOCKCHAIN, blockchain) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -124,7 +124,7 @@ internal class ChooseManagedTokensModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun getNotification(): NotificationUM? {
|
||||
return if (params.source == Source.SendViaSwap && params.showSendViaSwapNotification) {
|
||||
return if (params.source == Source.SendViaSwap && params.shouldShowSendViaSwapNotification) {
|
||||
ChooseManagedTokensNotificationUM.SendViaSwap(onCloseClick = ::removeNotification)
|
||||
} else {
|
||||
null
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ private fun LazyListScope.contentItems(items: ImmutableList<CurrencyItemUM>) {
|
|||
name = name,
|
||||
type = symbol,
|
||||
icon = icon,
|
||||
showCustom = false,
|
||||
shouldShowCustom = false,
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ internal class PreviewManageTokensComponent(
|
|||
background = Color.Black,
|
||||
topBadgeIconResId = R.drawable.img_eth_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = true,
|
||||
shouldShowCustomBadge = true,
|
||||
),
|
||||
onRemoveClick = {},
|
||||
)
|
||||
|
|
@ -141,7 +141,7 @@ internal class PreviewManageTokensComponent(
|
|||
url = null,
|
||||
fallbackResId = R.drawable.img_btc_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
networks = if (index == 2) {
|
||||
CurrencyItemUM.Basic.NetworksUM.Expanded(getCurrencyNetworks(index))
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ internal class PreviewOnboardingManageTokensComponent(
|
|||
background = Color.Black,
|
||||
topBadgeIconResId = R.drawable.img_eth_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = true,
|
||||
shouldShowCustomBadge = true,
|
||||
),
|
||||
onRemoveClick = {},
|
||||
)
|
||||
|
|
@ -79,7 +79,7 @@ internal class PreviewOnboardingManageTokensComponent(
|
|||
url = null,
|
||||
fallbackResId = R.drawable.img_btc_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
networks = if (index == 2) {
|
||||
CurrencyItemUM.Basic.NetworksUM.Expanded(getCurrencyNetworks())
|
||||
|
|
|
|||
|
|
@ -163,9 +163,9 @@ private fun NetworkItem(model: CurrencyNetworkUM, modifier: Modifier = Modifier)
|
|||
url = null,
|
||||
fallbackResId = model.iconResId,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
showCustom = false,
|
||||
shouldShowCustom = false,
|
||||
)
|
||||
},
|
||||
action = {
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ private fun CustomCurrencyItem(item: CurrencyItemUM.Custom, modifier: Modifier =
|
|||
name = name,
|
||||
type = symbol,
|
||||
icon = icon,
|
||||
showCustom = true,
|
||||
shouldShowCustom = true,
|
||||
)
|
||||
},
|
||||
action = {
|
||||
|
|
@ -316,7 +316,7 @@ private fun BasicCurrencyItem(item: CurrencyItemUM.Basic, isEditable: Boolean, m
|
|||
name = name,
|
||||
type = symbol,
|
||||
icon = icon,
|
||||
showCustom = false,
|
||||
shouldShowCustom = false,
|
||||
)
|
||||
},
|
||||
action = {
|
||||
|
|
@ -442,7 +442,7 @@ private class PreviewManageTokensComponentProvider : PreviewParameterProvider<Ma
|
|||
showTangemIcon = true,
|
||||
params = ManageTokensComponent.Params(
|
||||
source = ManageTokensSource.ONBOARDING,
|
||||
userWalletId = UserWalletId("wallet_id"),
|
||||
userWalletId = UserWalletId("0x"),
|
||||
),
|
||||
),
|
||||
PreviewManageTokensComponent(
|
||||
|
|
@ -455,7 +455,7 @@ private class PreviewManageTokensComponentProvider : PreviewParameterProvider<Ma
|
|||
showTangemIcon = false,
|
||||
params = ManageTokensComponent.Params(
|
||||
source = ManageTokensSource.ONBOARDING,
|
||||
userWalletId = UserWalletId("wallet_id"),
|
||||
userWalletId = UserWalletId("0x"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ internal fun CustomDerivationInputDialog(model: CustomDerivationInputUM, onDismi
|
|||
fieldValue = value,
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResourceSafe(id = R.string.common_ok),
|
||||
enabled = model.isConfirmEnabled,
|
||||
isEnabled = model.isConfirmEnabled,
|
||||
onClick = model.onConfirm,
|
||||
),
|
||||
dismissButton = DialogButtonUM(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ internal class ManageTokensWarningDelegate @AssistedInject constructor(
|
|||
firstActionBuilder = {
|
||||
EventMessageAction(
|
||||
title = resourceReference(R.string.token_details_hide_alert_hide),
|
||||
warning = true,
|
||||
isWarning = true,
|
||||
onClick = onConfirm,
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ private fun ManagedCryptoCurrency.Custom.toUiModel(
|
|||
url = iconUrl,
|
||||
fallbackResId = network.id.getIconRes(isColored = true),
|
||||
isGrayscale = false,
|
||||
showCustomBadge = true,
|
||||
shouldShowCustomBadge = true,
|
||||
)
|
||||
}
|
||||
is ManagedCryptoCurrency.Custom.Token -> {
|
||||
|
|
@ -44,7 +44,7 @@ private fun ManagedCryptoCurrency.Custom.toUiModel(
|
|||
fallbackTint = getTintForTokenIcon(background),
|
||||
topBadgeIconResId = network.id.getIconRes(isColored = true),
|
||||
isGrayscale = false,
|
||||
showCustomBadge = true,
|
||||
shouldShowCustomBadge = true,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -68,7 +68,7 @@ private fun ManagedCryptoCurrency.Token.toUiModel(
|
|||
url = iconUrl,
|
||||
topBadgeIconResId = null,
|
||||
isGrayscale = if (isEditable) !isAdded else false,
|
||||
showCustomBadge = false,
|
||||
shouldShowCustomBadge = false,
|
||||
fallbackTint = getTintForTokenIcon(background),
|
||||
fallbackBackground = background,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ interface MarketsTokenDetailsComponent : ComposableContentComponent {
|
|||
data class Params(
|
||||
val token: TokenMarketParams,
|
||||
val appCurrency: AppCurrency,
|
||||
val showPortfolio: Boolean,
|
||||
val shouldShowPortfolio: Boolean,
|
||||
val analyticsParams: AnalyticsParams?,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ internal class DefaultMarketsTokenDetailDeepLinkHandler @AssistedInject construc
|
|||
imageUrl = getTokenIconUrlFromDefaultHost(rawTokenId),
|
||||
),
|
||||
appCurrency = appCurrency,
|
||||
showPortfolio = true,
|
||||
shouldShowPortfolio = true,
|
||||
analyticsParams = null,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
|
|||
|
||||
private val model: MarketsTokenDetailsModel = getOrCreateModel(updatedParams)
|
||||
|
||||
private val portfolioComponent: MarketsPortfolioComponent? = if (updatedParams.showPortfolio) {
|
||||
private val portfolioComponent: MarketsPortfolioComponent? = if (updatedParams.shouldShowPortfolio) {
|
||||
portfolioComponentFactory.create(
|
||||
context = child("my_portfolio"),
|
||||
params = MarketsPortfolioComponent.Params(
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ internal object ExchangeItemStateConverter : Converter<TokenMarketExchange, Toke
|
|||
url = value.imageUrl,
|
||||
fallbackResId = R.drawable.ic_alert_24,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value.name)),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ private class ExchangesBottomSheetContentProvider : CollectionPreviewParameterPr
|
|||
url = null,
|
||||
fallbackResId = R.drawable.ic_facebook_24,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "OKX")),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "$67.52M"),
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ private fun AdditionalInfoNotification(onClick: () -> Unit, modifier: Modifier =
|
|||
subtitle = TextReference.Res(id = R.string.information_generated_with_ai),
|
||||
iconResId = R.drawable.ic_magic_28,
|
||||
onClick = onClick,
|
||||
showArrowIcon = false,
|
||||
shouldShowArrowIcon = false,
|
||||
),
|
||||
modifier = modifier,
|
||||
subtitleColor = TangemTheme.colors.text.primary1,
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
|
|||
params = MarketsTokenDetailsComponent.Params(
|
||||
token = token,
|
||||
appCurrency = appCurrency,
|
||||
showPortfolio = true,
|
||||
shouldShowPortfolio = true,
|
||||
analyticsParams = MarketsTokenDetailsComponent.AnalyticsParams(
|
||||
blockchain = null,
|
||||
source = "Market",
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ internal class PortfolioAnalyticsEvent(
|
|||
TokenActionsBSContentUM.Action.Buy -> "Button - Buy"
|
||||
TokenActionsBSContentUM.Action.Receive -> "Button - Receive"
|
||||
TokenActionsBSContentUM.Action.Exchange -> "Button - Swap"
|
||||
TokenActionsBSContentUM.Action.Stake -> "Button - Stake"
|
||||
else -> "error"
|
||||
},
|
||||
params = buildMap {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.features.markets.portfolio.impl.loader
|
||||
|
||||
import arrow.core.getOrElse
|
||||
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
|
||||
|
|
@ -15,7 +13,6 @@ import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase
|
|||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
|
@ -41,8 +38,8 @@ internal class PortfolioDataLoader @Inject constructor(
|
|||
fun load(currencyRawId: CryptoCurrency.RawID): Flow<PortfolioData> {
|
||||
return combine(
|
||||
flow = getAllWalletsCryptoCurrenciesData(currencyRawId = currencyRawId),
|
||||
flow2 = getSelectedAppCurrencyFlow(),
|
||||
flow3 = getBalanceHidingSettingsFlow(),
|
||||
flow2 = getSelectedAppCurrencyUseCase.invokeOrDefault(),
|
||||
flow3 = getBalanceHidingSettingsUseCase.isBalanceHidden(),
|
||||
) { walletsWithCurrencies, appCurrency, isBalanceHidden ->
|
||||
PortfolioData(
|
||||
walletsWithCurrencies = walletsWithCurrencies,
|
||||
|
|
@ -113,23 +110,6 @@ internal class PortfolioDataLoader @Inject constructor(
|
|||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun getSelectedAppCurrencyFlow(): Flow<AppCurrency> {
|
||||
return getSelectedAppCurrencyUseCase()
|
||||
.map {
|
||||
it.getOrElse { e ->
|
||||
Timber.e("Failed to load app currency: $e")
|
||||
AppCurrency.Default
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun getBalanceHidingSettingsFlow(): Flow<Boolean> {
|
||||
return getBalanceHidingSettingsUseCase()
|
||||
.map { it.isBalanceHidden }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun getWalletsWithTotalBalanceFlow(
|
||||
ids: List<UserWalletId>,
|
||||
): Flow<Map<UserWalletId, Lce<TokenListError, TotalFiatBalance>>> {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.features.markets.impl.R
|
||||
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -42,7 +41,6 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
|||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val shareManager: ShareManager,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
) {
|
||||
|
||||
private val disabledActionsInDemoMode = buildSet {
|
||||
|
|
@ -159,17 +157,10 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun onSendClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
val route = if (sendFeatureToggles.isSendWithSwapEnabled) {
|
||||
AppRoute.SendEntryPoint(
|
||||
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||
currency = cryptoCurrencyData.status.currency,
|
||||
)
|
||||
} else {
|
||||
AppRoute.Send(
|
||||
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||
currency = cryptoCurrencyData.status.currency,
|
||||
)
|
||||
}
|
||||
val route = AppRoute.SendEntryPoint(
|
||||
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||
currency = cryptoCurrencyData.status.currency,
|
||||
)
|
||||
router.push(route)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ private fun AddButton(state: AddButtonState, onClick: () -> Unit) {
|
|||
text = resourceReference(R.string.markets_add_token),
|
||||
icon = TangemButtonIconPosition.Start(R.drawable.ic_plus_24),
|
||||
onClick = onClick,
|
||||
enabled = state == AddButtonState.Available,
|
||||
isEnabled = state == AddButtonState.Available,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ internal class TokenMarketBlockModel @Inject constructor(
|
|||
|
||||
private val params = paramsContainer.require<TokenMarketBlockComponent.Params>()
|
||||
|
||||
private val priceAndTimePointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = false)
|
||||
private val priceAndTimePointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = false)
|
||||
|
||||
private val currentAppCurrency = getSelectedAppCurrencyUseCase()
|
||||
.map { maybeAppCurrency ->
|
||||
|
|
@ -152,7 +152,7 @@ internal class TokenMarketBlockModel @Inject constructor(
|
|||
AppRoute.MarketsTokenDetails(
|
||||
token = tokenParam,
|
||||
appCurrency = currentAppCurrency.value,
|
||||
showPortfolio = false,
|
||||
shouldShowPortfolio = false,
|
||||
analyticsParams = AppRoute.MarketsTokenDetails.AnalyticsParams(
|
||||
blockchain = params.cryptoCurrency.network.name,
|
||||
source = "Token",
|
||||
|
|
|
|||
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