diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt index aeedd6ab92..10ecc64922 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt @@ -6,18 +6,21 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.conditional import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -34,6 +37,7 @@ data class SmallButtonConfig( val onClick: () -> Unit, val icon: TangemButtonIconPosition = TangemButtonIconPosition.None, val isEnabled: Boolean = true, + val isLoading: Boolean = false, ) /** @@ -68,7 +72,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier: label = "Update background color", ) - Row( + Box( modifier = modifier .defaultMinSize( minWidth = TangemTheme.dimens.size46, @@ -79,7 +83,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier: color = backgroundColor, shape = shape, ) - .clickable(enabled = config.isEnabled, onClick = config.onClick) + .clickable(enabled = !config.isLoading && config.isEnabled, onClick = config.onClick) .padding( paddingValues = when (config.icon) { is TangemButtonIconPosition.None -> PaddingValues( @@ -95,42 +99,54 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier: ) }, ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, + contentAlignment = Alignment.Center, ) { - ContentContainer( - iconPosition = config.icon, - text = { - val textColor by animateColorAsState( - targetValue = when { - !config.isEnabled -> TangemTheme.colors.text.disabled - isPrimary -> TangemTheme.colors.text.primary2 - else -> TangemTheme.colors.text.primary1 - }, - label = "Update text color", - ) + if (config.isLoading) { + CircularProgressIndicator( + strokeWidth = TangemTheme.dimens.size2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.size(TangemTheme.dimens.size16), + ) + } + Row( + modifier = Modifier.conditional(config.isLoading) { alpha(0f) }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + ContentContainer( + iconPosition = config.icon, + text = { + val textColor by animateColorAsState( + targetValue = when { + !config.isEnabled -> TangemTheme.colors.text.disabled + isPrimary -> TangemTheme.colors.text.primary2 + else -> TangemTheme.colors.text.primary1 + }, + label = "Update text color", + ) - Text( - modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing4), - text = config.text.resolveReference(), - color = textColor, - maxLines = 1, - style = TangemTheme.typography.button, - ) - }, - icon = { iconResId -> - Icon( - modifier = Modifier.size(TangemTheme.dimens.size16), - painter = painterResource(id = iconResId), - tint = if (config.isEnabled) { - TangemTheme.colors.icon.secondary - } else { - TangemTheme.colors.icon.inactive - }, - contentDescription = null, - ) - }, - ) + Text( + modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing4), + text = config.text.resolveReference(), + color = textColor, + maxLines = 1, + style = TangemTheme.typography.button, + ) + }, + icon = { iconResId -> + Icon( + modifier = Modifier.size(TangemTheme.dimens.size16), + painter = painterResource(id = iconResId), + tint = if (config.isEnabled) { + TangemTheme.colors.icon.secondary + } else { + TangemTheme.colors.icon.inactive + }, + contentDescription = null, + ) + }, + ) + } } } @@ -172,6 +188,7 @@ private fun ButtonsSample() { ) PrimarySmallButton(config = config) SecondarySmallButton(config = config.copy(text = TextReference.Str(value = "Add"))) + SecondarySmallButton(config = config.copy(text = TextReference.Str(value = "Add"), isLoading = true)) SecondarySmallButton( config = config.copy( text = TextReference.Str(value = "Rating"), @@ -191,5 +208,12 @@ private fun ButtonsSample() { isEnabled = false, ), ) + SecondarySmallButton( + config = config.copy( + text = TextReference.Str(value = "Add token"), + icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24), + isLoading = true, + ), + ) } } \ No newline at end of file diff --git a/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt b/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt index c41e56cc31..cdb3dcba43 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt @@ -168,6 +168,14 @@ internal class DefaultAccountsCRUDRepository( userTokensSaver.push(userWalletId = userWalletId, response = response.toUserTokensResponse()) } + override suspend fun getTotalAccountsCountSync(userWalletId: UserWalletId): Option = option { + val accountListResponse = getAccountsResponseSync(userWalletId = userWalletId) + + ensureNotNull(accountListResponse) + + return accountListResponse.wallet.totalAccounts.toOption() + } + override suspend fun getTotalActiveAccountsCountSync(userWalletId: UserWalletId): Option = option { val accountListResponse = getAccountsResponseSync(userWalletId = userWalletId) diff --git a/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt b/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt index 776578b0b2..2000100c0e 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt @@ -78,6 +78,8 @@ internal class DefaultMainAccountTokensMigration( }, ) + store.updateData { updatedResponse } + userTokensSaver.push( userWalletId = userWalletId, response = updatedResponse.toUserTokensResponse(), diff --git a/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt b/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt index b59fd6884d..b0bd00999e 100644 --- a/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt @@ -183,6 +183,8 @@ class DefaultMainAccountTokensMigrationTest { accountsResponseStoreFlow.value = response + coEvery { accountsResponseStore.updateData(any()) } returns mockk() + // Act val actual = migration.migrate(userWalletId, derivationIndex) @@ -199,6 +201,7 @@ class DefaultMainAccountTokensMigrationTest { coVerifySequence { accountsResponseStoreFactory.create(userWalletId) accountsResponseStore.data + accountsResponseStore.updateData(any()) userTokensSaver.push( userWalletId = userWalletId, response = migratedResponse.toUserTokensResponse(), diff --git a/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt b/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt index e305566047..80cf0e378a 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt @@ -48,11 +48,11 @@ data class AccountList private constructor( */ operator fun plus(other: Account): Either { val isNewAccount = this.accounts.none { it.accountId == other.accountId } - val accounts = this.accounts.addOrReplace(other) { it.accountId == other.accountId } + val accounts = this.accounts.toList().addOrReplace(other) { it.accountId == other.accountId } return invoke( userWalletId = this.userWalletId, - accounts = accounts, + accounts = accounts.toSet(), totalAccounts = this.totalAccounts + if (isNewAccount) 1 else 0, sortType = this.sortType, groupType = this.groupType, diff --git a/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt b/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt index 049ad091a9..598771815e 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt @@ -78,6 +78,13 @@ interface AccountsCRUDRepository { /** Synchronizes tokens for a specific [userWalletId] with remote data source */ suspend fun syncTokens(userWalletId: UserWalletId) + /** + * Retrieves the total count of active accounts associated with a specific user wallet excluding archived accounts + * + * @param userWalletId the unique identifier of the user wallet + */ + suspend fun getTotalAccountsCountSync(userWalletId: UserWalletId): Option + /** * Retrieves the total count of active accounts associated with a specific user wallet excluding archived accounts * diff --git a/domain/account/src/main/java/com/tangem/domain/account/usecase/GetUnoccupiedAccountIndexUseCase.kt b/domain/account/src/main/java/com/tangem/domain/account/usecase/GetUnoccupiedAccountIndexUseCase.kt index e761dd5649..6e3d4faf1d 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/usecase/GetUnoccupiedAccountIndexUseCase.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/usecase/GetUnoccupiedAccountIndexUseCase.kt @@ -40,7 +40,7 @@ class GetUnoccupiedAccountIndexUseCase( private suspend fun Raise.getTotalAccountsCount(userWalletId: UserWalletId): Int { return catch( - block = { crudRepository.getTotalActiveAccountsCountSync(userWalletId = userWalletId) }, + block = { crudRepository.getTotalAccountsCountSync(userWalletId = userWalletId) }, catch = { raise(Error.DataOperationFailed(cause = it)) }, ) .getOrElse { raise(Error.DataNotFound) } diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/account/AccountId.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/account/AccountId.kt index 3133206dd3..bffd56a6c4 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/account/AccountId.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/account/AccountId.kt @@ -40,6 +40,10 @@ data class AccountId private constructor( private val sha256Digest: MessageDigest by lazy { MessageDigest.getInstance("SHA-256") } private val hexRegex = Regex("^[a-fA-F0-9]{64}$") + fun forMainCryptoPortfolio(userWalletId: UserWalletId): AccountId { + return forCryptoPortfolio(userWalletId = userWalletId, derivationIndex = DerivationIndex.Main) + } + /** * Creates a unique account identifier for a crypto portfolio * diff --git a/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt index 802d8672b9..9c2bdc1c95 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt @@ -20,6 +20,7 @@ import com.tangem.domain.models.account.AccountId import com.tangem.features.account.ArchivedAccountListComponent import com.tangem.features.account.archived.entity.AccountArchivedUM import com.tangem.features.account.archived.entity.AccountArchivedUMBuilder +import com.tangem.features.account.archived.entity.AccountArchivedUMBuilder.Companion.toggleProgress import com.tangem.features.account.createedit.error.AccountFeatureError import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder @@ -92,9 +93,12 @@ internal class ArchivedAccountListModel @Inject constructor( } private fun recoverCryptoPortfolio(accountId: AccountId) = modelScope.launch { - withContext(dispatchers.default) { + _uiState.update { it.toggleProgress(accountId, isLoading = true) } + val result = withContext(dispatchers.default) { recoverCryptoPortfolioUseCase(accountId) } + _uiState.update { it.toggleProgress(accountId, isLoading = false) } + result .onLeft(::handleRecoverError) .onRight { showSuccessRecoverMessage() diff --git a/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUM.kt b/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUM.kt index a29481e093..15c30689e1 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUM.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUM.kt @@ -24,5 +24,6 @@ internal data class ArchivedAccountUM( val accountIconUM: CryptoPortfolioIconUM, val tokensInfo: TextReference, val networksInfo: TextReference, + val isLoading: Boolean, val onClick: () -> Unit, ) \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUMBuilder.kt b/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUMBuilder.kt index ed8d653691..207e1f1a4a 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUMBuilder.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUMBuilder.kt @@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.pluralReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.account.models.ArchivedAccount import com.tangem.domain.account.usecase.ArchivedAccountList +import com.tangem.domain.models.account.AccountId import kotlinx.collections.immutable.toImmutableList import timber.log.Timber import javax.inject.Inject @@ -28,6 +29,7 @@ internal class AccountArchivedUMBuilder @Inject constructor() { accountId = accountId.value, accountName = name.toUM().value, accountIconUM = icon.toUM(), + isLoading = false, tokensInfo = pluralReference( R.plurals.common_tokens_count, count = tokensCount, @@ -52,4 +54,25 @@ internal class AccountArchivedUMBuilder @Inject constructor() { onRetryClick = { getArchivedAccounts() }, ) } + + companion object { + + fun AccountArchivedUM.toggleProgress(accountId: AccountId, isLoading: Boolean): AccountArchivedUM { + return when (this) { + is AccountArchivedUM.Error, + is AccountArchivedUM.Loading, + -> this + + is AccountArchivedUM.Content -> copy( + accounts = accounts.map { accountUM -> + if (accountUM.accountId == accountId.value) { + accountUM.copy(isLoading = isLoading) + } else { + accountUM + } + }.toImmutableList(), + ) + } + } + } } \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/archived/ui/ArchivedAccountListContent.kt b/features/account/impl/src/main/java/com/tangem/features/account/archived/ui/ArchivedAccountListContent.kt index fca08ec492..5614b931a9 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/archived/ui/ArchivedAccountListContent.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/archived/ui/ArchivedAccountListContent.kt @@ -126,7 +126,7 @@ private fun ArchivedAccountRow(item: ArchivedAccountUM, modifier: Modifier = Mod Row( modifier = modifier .fillMaxWidth() - .clickable(onClick = item.onClick) + .clickable(enabled = !item.isLoading, onClick = item.onClick) .padding(all = TangemTheme.dimens.spacing12), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), @@ -148,6 +148,7 @@ private fun ArchivedAccountRow(item: ArchivedAccountUM, modifier: Modifier = Mod SecondarySmallButton( config = SmallButtonConfig( text = resourceReference(R.string.account_archived_recover), + isLoading = item.isLoading, onClick = item.onClick, ), ) @@ -177,7 +178,7 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider Unit, onLinkMoreCardsClick: () -> Unit, onReferralClick: () -> Unit, + onManageTokensClick: () -> Unit, onAccessCodeClick: () -> Unit, walletUpgradeDismissed: Boolean, onUpgradeWalletClick: () -> Unit, @@ -69,6 +65,7 @@ internal class ItemsBuilder @Inject constructor( isManageTokensAvailable = isManageTokensAvailable, onLinkMoreCardsClick = onLinkMoreCardsClick, onReferralClick = onReferralClick, + onManageTokensClick = onManageTokensClick, ), ) .addAll( @@ -175,6 +172,7 @@ internal class ItemsBuilder @Inject constructor( isManageTokensAvailable: Boolean, onLinkMoreCardsClick: () -> Unit, onReferralClick: () -> Unit, + onManageTokensClick: () -> Unit, ) = WalletSettingsItemUM.WithItems( id = "card", description = resourceReference(R.string.settings_card_settings_footer), @@ -204,10 +202,7 @@ internal class ItemsBuilder @Inject constructor( BlockUM( text = resourceReference(R.string.add_tokens_title), iconRes = R.drawable.ic_tether_24, - onClick = { - analyticsEventHandler.send(Settings.ButtonManageTokens) - router.push(AppRoute.ManageTokens(Source.SETTINGS, PortfolioId(userWalletId))) - }, + onClick = onManageTokensClick, ).let(::add) }