Updated on 2026-08-14
This commit is contained in:
commit
9ba66ba084
179 changed files with 5757 additions and 1156 deletions
|
|
@ -363,6 +363,9 @@ internal class WalletModel @Inject constructor(
|
|||
is WalletsUpdateActionResolver.Action.RenameWallets -> {
|
||||
stateHolder.update(transformer = RenameWalletsTransformer(renamedWallets = action.renamedWallets))
|
||||
}
|
||||
WalletsUpdateActionResolver.Action.EmptyWallets -> {
|
||||
Timber.w("Wallets list is empty!")
|
||||
}
|
||||
is WalletsUpdateActionResolver.Action.Unknown -> {
|
||||
Timber.w("Unable to perform action: $action")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
) {
|
||||
|
||||
fun resolve(wallets: List<UserWallet>, currentState: WalletScreenState): Action {
|
||||
if (wallets.isEmpty()) {
|
||||
return Action.EmptyWallets
|
||||
}
|
||||
|
||||
val selectedWallet = getSelectedWalletSyncUseCase().getOrElse {
|
||||
/* Selected user wallet can be null after reset if remaining user wallets is locked */
|
||||
return Action.Unknown
|
||||
|
|
@ -319,6 +323,8 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
data object EmptyWallets : Action()
|
||||
|
||||
data object Unknown : Action()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,10 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.feature.wallet.DefaultWalletEntryComponent
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.OrganizeTokensModel
|
||||
import com.tangem.feature.wallet.child.wallet.model.WalletModel
|
||||
import com.tangem.feature.wallet.utils.DefaultUserWalletImageFetcher
|
||||
import com.tangem.feature.wallet.utils.DefaultUserWalletsFetcher
|
||||
import com.tangem.features.wallet.WalletEntryComponent
|
||||
import com.tangem.features.wallet.utils.UserWalletImageFetcher
|
||||
import com.tangem.features.wallet.utils.UserWalletsFetcher
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -13,6 +15,7 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
|
|
@ -24,6 +27,10 @@ internal interface WalletFeatureModule {
|
|||
@Binds
|
||||
fun bindUserWalletsFetcher(impl: DefaultUserWalletsFetcher.Factory): UserWalletsFetcher.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindUserWalletImageFetcher(impl: DefaultUserWalletImageFetcher): UserWalletImageFetcher
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(WalletModel::class)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@ package com.tangem.feature.wallet.presentation.common.preview
|
|||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
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.models.wallet.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
|
||||
|
|
@ -117,7 +120,13 @@ internal object WalletScreenPreviewData {
|
|||
buttons = persistentListOf(buyButton),
|
||||
warnings = persistentListOf(
|
||||
WalletNotification.Warning.SomeNetworksUnreachable,
|
||||
WalletNotification.FinishWalletActivation { },
|
||||
WalletNotification.FinishWalletActivation(
|
||||
iconTint = NotificationConfig.IconTint.Attention,
|
||||
buttonsState = ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.hw_activation_need_finish),
|
||||
onClick = { },
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomSheetConfig = null,
|
||||
tokensListState = textContentTokensState,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.IconTint
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
|
|
@ -19,7 +22,9 @@ import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
|
|||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -54,7 +59,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
|
||||
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
|
||||
|
||||
addFinishWalletActivationNotification(userWallet, clickIntents)
|
||||
addFinishWalletActivationNotification(userWallet, maybeTokenList, clickIntents)
|
||||
|
||||
addReferralPromoNotification(cardTypesResolver, clickIntents, shouldShowReferralPromo)
|
||||
|
||||
|
|
@ -256,26 +261,55 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addIf(element: WalletNotification, condition: Boolean) {
|
||||
if (condition) add(element = element)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addFinishWalletActivationNotification(
|
||||
userWallet: UserWallet,
|
||||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
if (userWallet !is UserWallet.Hot) return
|
||||
|
||||
val shouldShowFinishActivation = !userWallet.backedUp
|
||||
|
||||
val iconTint = maybeTokenList.fold(
|
||||
ifLoading = {
|
||||
if ((it?.totalFiatBalance as? TotalFiatBalance.Loaded)?.amount?.isPositive() == true) {
|
||||
IconTint.Warning
|
||||
} else {
|
||||
IconTint.Attention
|
||||
}
|
||||
},
|
||||
ifContent = {
|
||||
if ((it.totalFiatBalance as? TotalFiatBalance.Loaded)?.amount?.isPositive() == true) {
|
||||
IconTint.Warning
|
||||
} else {
|
||||
IconTint.Attention
|
||||
}
|
||||
},
|
||||
ifError = { IconTint.Attention },
|
||||
)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.FinishWalletActivation(
|
||||
onFinishClick = clickIntents::onFinishWalletActivationClick,
|
||||
iconTint = iconTint,
|
||||
buttonsState = when (iconTint) {
|
||||
IconTint.Warning -> ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.hw_activation_need_finish),
|
||||
onClick = clickIntents::onFinishWalletActivationClick,
|
||||
)
|
||||
else -> ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.hw_activation_need_finish),
|
||||
onClick = clickIntents::onFinishWalletActivationClick,
|
||||
)
|
||||
},
|
||||
),
|
||||
condition = shouldShowFinishActivation,
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addIf(element: WalletNotification, condition: Boolean) {
|
||||
if (condition) add(element = element)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_REMAINING_SIGNATURES_COUNT = 10
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.IconTint
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.pluralReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
|
@ -256,16 +259,15 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
)
|
||||
|
||||
data class FinishWalletActivation(
|
||||
val onFinishClick: () -> Unit,
|
||||
val iconTint: IconTint,
|
||||
val buttonsState: ButtonsState,
|
||||
) : WalletNotification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.hw_activation_need_title),
|
||||
subtitle = resourceReference(R.string.hw_activation_need_description),
|
||||
iconResId = R.drawable.img_knight_shield_32,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.hw_activation_need_finish),
|
||||
onClick = onFinishClick,
|
||||
),
|
||||
iconTint = iconTint,
|
||||
buttonsState = buttonsState,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -282,6 +284,7 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
text = resourceReference(R.string.notification_referral_promo_button),
|
||||
onClick = onClick,
|
||||
),
|
||||
iconSize = 54.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.common
|
|||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.notifications.NoteMigrationNotification
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -35,15 +34,16 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
|
|||
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
)
|
||||
}
|
||||
is WalletNotification.FinishWalletActivation -> {
|
||||
Notification(
|
||||
config = it.config,
|
||||
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
Notification(
|
||||
config = it.config,
|
||||
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
iconSize = if (it is WalletNotification.ReferralPromo) {
|
||||
54.dp
|
||||
} else {
|
||||
20.dp
|
||||
},
|
||||
iconTint = when (it) {
|
||||
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
|
||||
is WalletNotification.Informational -> TangemTheme.colors.icon.accent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tangem.feature.wallet.utils
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.ui.userwallet.converter.ArtworkUMConverter
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.wallet.utils.UserWalletImageFetcher
|
||||
import com.tangem.operations.attestation.ArtworkSize
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class DefaultUserWalletImageFetcher @Inject constructor(
|
||||
private val getCardImageUseCase: GetCardImageUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val artworkUMConverter: ArtworkUMConverter,
|
||||
) : UserWalletImageFetcher {
|
||||
|
||||
private val smallCache = MutableStateFlow(mapOf<String, ArtworkUM>())
|
||||
private val largeCache = MutableStateFlow(mapOf<String, ArtworkUM>())
|
||||
|
||||
override fun walletImage(wallet: UserWallet, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> = when (wallet) {
|
||||
is UserWallet.Cold -> walletImage(wallet.scanResponse.card, size)
|
||||
is UserWallet.Hot -> flowOf(UserWalletItemUM.ImageState.MobileWallet)
|
||||
}
|
||||
|
||||
override fun walletsImage(
|
||||
wallets: Collection<UserWallet>,
|
||||
size: ArtworkSize,
|
||||
): Flow<Map<UserWalletId, UserWalletItemUM.ImageState>> = wallets
|
||||
.map { userWallet -> walletImage(userWallet, size).map { imageState -> userWallet.walletId to imageState } }
|
||||
.merge()
|
||||
.runningFold(mapOf<UserWalletId, UserWalletItemUM.ImageState>()) { map, newState -> map.plus(newState) }
|
||||
.filter { it.size >= wallets.size } // prevent spam, waiting full map
|
||||
.distinctUntilChanged()
|
||||
|
||||
override fun walletImage(walletId: UserWalletId, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> = flow {
|
||||
val imagesFlow = getUserWalletUseCase.invokeFlow(walletId)
|
||||
// emit Loading and wait wallet
|
||||
.onEach { if (it.isLeft()) emit(UserWalletItemUM.ImageState.Loading) }
|
||||
.filterIsInstance<Either.Right<UserWallet>>()
|
||||
.map { it.value }
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { wallet -> walletImage(wallet, size) }
|
||||
emitAll(imagesFlow)
|
||||
}.distinctUntilChanged()
|
||||
|
||||
override fun walletImage(cardDTO: CardDTO, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> =
|
||||
internalGetCardImage(
|
||||
cardInfo = cardDTO,
|
||||
size = size,
|
||||
).distinctUntilChanged()
|
||||
|
||||
private fun internalGetCardImage(cardInfo: CardDTO, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> = flow {
|
||||
emit(cacheOrLoading(cardInfo.cardId, size))
|
||||
|
||||
val artwork = getCardImageUseCase.invoke(
|
||||
cardId = cardInfo.cardId,
|
||||
cardPublicKey = cardInfo.cardPublicKey,
|
||||
size = size,
|
||||
manufacturerName = cardInfo.manufacturer.name,
|
||||
firmwareVersion = cardInfo.firmwareVersion.toSdkFirmwareVersion(),
|
||||
)
|
||||
.let { artworkUMConverter.convert(it) }
|
||||
.also { save(cardInfo.cardId, size, it) }
|
||||
emit(UserWalletItemUM.ImageState.Image(artwork))
|
||||
}
|
||||
|
||||
private fun cacheOrLoading(cardId: String, size: ArtworkSize): UserWalletItemUM.ImageState {
|
||||
val artwork = when (size) {
|
||||
ArtworkSize.LARGE -> largeCache.value[cardId]
|
||||
ArtworkSize.SMALL -> smallCache.value[cardId]
|
||||
}
|
||||
return artwork
|
||||
?.let { UserWalletItemUM.ImageState.Image(artwork) }
|
||||
?: UserWalletItemUM.ImageState.Loading
|
||||
}
|
||||
|
||||
private fun save(cardId: String, size: ArtworkSize, artwork: ArtworkUM) {
|
||||
when (size) {
|
||||
ArtworkSize.LARGE -> largeCache.update { it.plus(cardId to artwork) }
|
||||
ArtworkSize.SMALL -> smallCache.update { it.plus(cardId to artwork) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,16 +14,15 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
|||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.lce
|
||||
import com.tangem.domain.core.utils.toLce
|
||||
import com.tangem.domain.models.ArtworkModel
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.features.wallet.utils.UserWalletImageFetcher
|
||||
import com.tangem.features.wallet.utils.UserWalletsFetcher
|
||||
import com.tangem.operations.attestation.ArtworkSize
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -45,11 +44,10 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
|
|||
@Assisted private val messageSender: UiMessageSender,
|
||||
@Assisted("onlyMultiCurrency") private val onlyMultiCurrency: Boolean,
|
||||
@Assisted("authMode") private val authMode: Boolean,
|
||||
private val getCardImageUseCase: GetCardImageUseCase,
|
||||
private val userWalletImageFetcher: UserWalletImageFetcher,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
) : UserWalletsFetcher {
|
||||
|
||||
private var loadedArtworks: HashMap<UserWalletId, ArtworkModel> = hashMapOf()
|
||||
private val walletsFlow =
|
||||
if (onlyMultiCurrency) getWalletsUseCase().map { it.filter { it.isMultiCurrency } } else getWalletsUseCase()
|
||||
|
||||
|
|
@ -67,7 +65,7 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
|
|||
flow = getSelectedAppCurrencyUseCase().distinctUntilChanged(),
|
||||
flow2 = getBalanceHidingSettingsUseCase().distinctUntilChanged(),
|
||||
flow3 = getWalletTotalBalanceUseCase(wallets.map(UserWallet::walletId)).distinctUntilChanged(),
|
||||
flow4 = loadArtworks(wallets),
|
||||
flow4 = userWalletImageFetcher.walletsImage(wallets, ArtworkSize.SMALL),
|
||||
) { maybeAppCurrency, balanceHidingSettings, maybeBalances, artworks ->
|
||||
createUiModels(
|
||||
wallets = wallets,
|
||||
|
|
@ -90,29 +88,12 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
|
|||
}
|
||||
.flowOn(dispatchers.default)
|
||||
|
||||
private fun loadArtworks(wallets: List<UserWallet>): Flow<HashMap<UserWalletId, ArtworkModel>> {
|
||||
return flow {
|
||||
emit(hashMapOf()) // emits right away so the transform doesn't wait for the images' loading to finish
|
||||
wallets.filterIsInstance<UserWallet.Cold>().forEach { wallet ->
|
||||
val artwork = getCardImageUseCase(
|
||||
cardId = wallet.cardId,
|
||||
manufacturerName = wallet.scanResponse.card.manufacturer.name,
|
||||
firmwareVersion = wallet.scanResponse.card.firmwareVersion.toSdkFirmwareVersion(),
|
||||
cardPublicKey = wallet.scanResponse.card.cardPublicKey,
|
||||
size = ArtworkSize.SMALL,
|
||||
)
|
||||
loadedArtworks[wallet.walletId] = artwork
|
||||
emit(loadedArtworks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createUiModels(
|
||||
wallets: List<UserWallet>,
|
||||
maybeAppCurrency: Either<SelectedAppCurrencyError, AppCurrency>,
|
||||
maybeBalances: Lce<TokenListError, Map<UserWalletId, TotalFiatBalance>>,
|
||||
balanceHidingSettings: BalanceHidingSettings,
|
||||
artworks: HashMap<UserWalletId, ArtworkModel>,
|
||||
artworks: Map<UserWalletId, UserWalletItemUM.ImageState>,
|
||||
): Lce<Error, ImmutableList<UserWalletItemUM>> = lce {
|
||||
val balances = withError(
|
||||
transform = { Error.UnableToGetBalances },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue