From 902d80d1f1da55d865100f0fa6cc33edec824a1a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 19:07:02 +0400 Subject: [PATCH 01/33] Updated on 2026-08-14 --- data/markets/build.gradle.kts | 1 + .../data/markets/DefaultMarketsTokenRepository.kt | 14 ++++++++++++-- .../tangem/data/markets/di/MarketsDataModule.kt | 5 +++++ .../details/impl/model/MarketsTokenDetailsModel.kt | 6 +++--- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/data/markets/build.gradle.kts b/data/markets/build.gradle.kts index 5378b4b964..ba16c78bf0 100644 --- a/data/markets/build.gradle.kts +++ b/data/markets/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { // region Others dependencies implementation(deps.kotlin.coroutines) + implementation(deps.jodatime) implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.timber) diff --git a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt index 2a8c0ed1d7..25703406d5 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt @@ -5,6 +5,7 @@ import com.tangem.blockchainsdk.compatibility.applyL2Compatibility import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.data.common.currency.getNetwork import com.tangem.data.common.utils.retryOnError @@ -13,8 +14,10 @@ import com.tangem.data.markets.converters.* import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.markets.TangemTechMarketsApi +import com.tangem.datasource.api.markets.models.response.TokenMarketExchangesResponse import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi.Companion.marketsQuoteFields +import com.tangem.datasource.local.datastore.RuntimeStateStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.markets.* import com.tangem.domain.markets.repositories.MarketsTokenRepository @@ -27,12 +30,15 @@ import kotlinx.coroutines.withContext import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong +@Suppress("LongParameterList") internal class DefaultMarketsTokenRepository( private val marketsApi: TangemTechMarketsApi, private val tangemTechApi: TangemTechApi, private val userWalletsStore: UserWalletsStore, private val dispatcherProvider: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, + private val cacheRegistry: CacheRegistry, + private val tokenExchangesStore: RuntimeStateStore>, ) : MarketsTokenRepository { private fun createTokenMarketsFetcher(firstBatchSize: Int, nextBatchSize: Int) = LimitOffsetBatchFetcher( @@ -266,9 +272,13 @@ internal class DefaultMarketsTokenRepository( override suspend fun getTokenExchanges(tokenId: String): List { return withContext(dispatcherProvider.io) { - val response = marketsApi.getCoinExchanges(coinId = tokenId).getOrThrow() + cacheRegistry.invokeOnExpire(key = "coins/$tokenId/exchanges", skipCache = false) { + val response = marketsApi.getCoinExchanges(coinId = tokenId).getOrThrow() - TokenMarketExchangeConverter.convertList(input = response.exchanges) + tokenExchangesStore.store(value = response.exchanges) + } + + TokenMarketExchangeConverter.convertList(input = tokenExchangesStore.get().value) } } diff --git a/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt b/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt index fa4e3d492f..056e3c82f7 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/di/MarketsDataModule.kt @@ -1,9 +1,11 @@ package com.tangem.data.markets.di import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.markets.DefaultMarketsTokenRepository import com.tangem.datasource.api.markets.TangemTechMarketsApi import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.local.datastore.RuntimeStateStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.markets.repositories.MarketsTokenRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -25,6 +27,7 @@ internal object MarketsDataModule { userWalletsStore: UserWalletsStore, dispatchers: CoroutineDispatcherProvider, analyticsEventHandler: AnalyticsEventHandler, + cacheRegistry: CacheRegistry, ): MarketsTokenRepository { return DefaultMarketsTokenRepository( marketsApi = marketsApi, @@ -32,6 +35,8 @@ internal object MarketsDataModule { dispatcherProvider = dispatchers, userWalletsStore = userWalletsStore, analyticsEventHandler = analyticsEventHandler, + cacheRegistry = cacheRegistry, + tokenExchangesStore = RuntimeStateStore(defaultValue = emptyList()), ) } } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt index 11ae6093f5..9d7e300646 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt @@ -531,11 +531,11 @@ internal class MarketsTokenDetailsModel @Inject constructor( showBottomSheet(content = ExchangesBottomSheetContent.Loading(exchangesCount)) - // Delay to show the bottom sheet - delay(timeMillis = 800L) - val maybeExchanges = getTokenExchangesUseCase(tokenId = params.token.id) + // Delay to show the bottom sheet + delay(timeMillis = 400L) + updateExchangeBSContent(maybeExchanges = maybeExchanges, exchangesCount = exchangesCount) } } From e6cb71265e2d915fb73355261a9ebdbb0f35f52d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 17:04:42 +0400 Subject: [PATCH 02/33] Updated on 2026-08-14 --- .../onramp/tokenlist/entity/TokenListUM.kt | 16 +-- .../tokenlist/entity/TokenListUMController.kt | 3 +- .../transformer/SearchBarUMTransformer.kt | 2 +- .../UpdateTokenItemsTransformer.kt | 100 +++++++++++++++++- .../tokenlist/model/OnrampTokenListModel.kt | 21 ++-- .../onramp/tokenlist/ui/OnrampTokenList.kt | 55 ++++++---- .../ui/preview/PreviewTokenListUMProvider.kt | 3 +- 7 files changed, 155 insertions(+), 45 deletions(-) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt index bb9e8f3562..11d24c670b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt @@ -7,27 +7,29 @@ import kotlinx.collections.immutable.persistentListOf /** * Token list UM * - * @property items items (search bar, tokens, headers) - * @property isBalanceHidden flag that indicates if balance should be hidden + * @property availableItems available items (search bar, header, tokens) + * @property unavailableItems unavailable items (header, tokens) + * @property isBalanceHidden flag that indicates if balance should be hidden * [REDACTED_AUTHOR] */ internal data class TokenListUM( - val items: ImmutableList, + val availableItems: ImmutableList, + val unavailableItems: ImmutableList, val isBalanceHidden: Boolean, ) { /** Get search bar if it exists */ fun getSearchBar(): TokensListItemUM.SearchBar? { - return items.firstOrNull() as? TokensListItemUM.SearchBar + return availableItems.firstOrNull() as? TokensListItemUM.SearchBar } /** Get tokens */ fun getTokens(): ImmutableList { - if (getSearchBar() == null) return items + if (getSearchBar() == null) return availableItems - return if (items.size > 1) { - items.subList(fromIndex = 1, toIndex = items.size) + return if (availableItems.size > 1) { + availableItems.subList(fromIndex = 1, toIndex = availableItems.size) } else { persistentListOf() } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt index bb662a5b7a..d6263dba6d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt @@ -19,7 +19,8 @@ internal class TokenListUMController @Inject constructor() { private val _state: MutableStateFlow = MutableStateFlow( value = TokenListUM( - items = persistentListOf(), + availableItems = persistentListOf(), + unavailableItems = persistentListOf(), isBalanceHidden = false, ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt index db17cb19ea..29a5b2edab 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt @@ -21,7 +21,7 @@ internal abstract class SearchBarUMTransformer : TokenListUMTransformer { val updatedSearchBar = searchBarItem.copy(searchBarUM = transform(searchBarItem.searchBarUM)) prevState.copy( - items = persistentListOf( + availableItems = persistentListOf( updatedSearchBar, *prevState.getTokens().toTypedArray(), ), diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt index b6065912be..cdf9aedfaa 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -1,18 +1,26 @@ package com.tangem.features.onramp.tokenlist.entity.transformer import com.tangem.common.ui.tokens.TokenItemStateConverter +import com.tangem.common.ui.tokens.TokenItemStateConverter.Companion.getFormattedFiatAmount +import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +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.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer import kotlinx.collections.immutable.toImmutableList +@Suppress("LongParameterList") internal class UpdateTokenItemsTransformer( - private val tokenItemStateConverter: TokenItemStateConverter, - private val statuses: List, + private val appCurrency: AppCurrency, + private val onItemClick: (CryptoCurrencyStatus) -> Unit, + private val statuses: Map>, private val isBalanceHidden: Boolean, private val hasSearchBar: Boolean, private val onQueryChange: (String) -> Unit, @@ -20,7 +28,15 @@ internal class UpdateTokenItemsTransformer( ) : TokenListUMTransformer { override fun transform(prevState: TokenListUM): TokenListUM { - val items = tokenItemStateConverter.convertList(input = statuses).map(TokensListItemUM::Token) + val availableItems = convertStatuses( + converter = createDefaultTokenItemStateConverter(), + statuses = statuses[true].orEmpty(), + ) + + val unavailableItems = convertStatuses( + converter = createUnavailableTokenItemStateConverter(), + statuses = statuses[false].orEmpty(), + ) val searchBarItem = if (hasSearchBar) { prevState.getSearchBar() ?: createSearchBarItem() @@ -29,11 +45,80 @@ internal class UpdateTokenItemsTransformer( } return prevState.copy( - items = (listOfNotNull(searchBarItem) + items).toImmutableList(), + availableItems = buildList { + if (searchBarItem != null) { + add(searchBarItem) + } + + createGroupTitle( + textReference = resourceReference(id = R.string.exchange_tokens_available_tokens_header), + ) + .let(::add) + + addAll(availableItems) + } + .toImmutableList(), + unavailableItems = buildList { + if (unavailableItems.isNotEmpty()) { + // TODO: [REDACTED_JIRA] + createGroupTitle( + textReference = stringReference(value = "Unavailable tokens"), + ) + .let(::add) + } + + addAll(unavailableItems) + }.toImmutableList(), isBalanceHidden = isBalanceHidden, ) } + private fun convertStatuses( + converter: TokenItemStateConverter, + statuses: List, + ): List { + return converter.convertList(statuses) + .map(TokensListItemUM::Token) + } + + private fun createDefaultTokenItemStateConverter(): TokenItemStateConverter { + return TokenItemStateConverter(appCurrency = appCurrency, onItemClick = onItemClick) + } + + private fun createUnavailableTokenItemStateConverter(): TokenItemStateConverter { + return TokenItemStateConverter( + appCurrency = appCurrency, + iconStateProvider = { CryptoCurrencyToIconStateConverter(isAvailable = false).convert(it) }, + titleStateProvider = { TokenItemState.TitleState.Content(text = it.currency.name, isAvailable = false) }, + subtitleStateProvider = { + when (it.value) { + CryptoCurrencyStatus.Loading -> TokenItemState.SubtitleState.Loading + else -> TokenItemState.SubtitleState.TextContent(value = it.currency.symbol, isAvailable = false) + } + }, + fiatAmountStateProvider = { + when (it.value) { + is CryptoCurrencyStatus.Loaded, + is CryptoCurrencyStatus.Custom, + is CryptoCurrencyStatus.NoQuote, + is CryptoCurrencyStatus.NoAccount, + -> { + TokenItemState.FiatAmountState.TextContent( + text = it.getFormattedFiatAmount(appCurrency), + isAvailable = false, + ) + } + is CryptoCurrencyStatus.Unreachable, + is CryptoCurrencyStatus.NoAmount, + is CryptoCurrencyStatus.MissedDerivation, + is CryptoCurrencyStatus.Loading, + -> null + } + }, + onItemClick = onItemClick, + ) + } + private fun createSearchBarItem(): TokensListItemUM.SearchBar { return TokensListItemUM.SearchBar( searchBarUM = SearchBarUM( @@ -45,4 +130,11 @@ internal class UpdateTokenItemsTransformer( ), ) } + + private fun createGroupTitle(textReference: TextReference): TokensListItemUM.NetworkGroupTitle { + return TokensListItemUM.NetworkGroupTitle( + id = textReference.hashCode(), + name = textReference, + ) + } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index fc6329296f..3ca0bd1482 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -1,7 +1,6 @@ package com.tangem.features.onramp.tokenlist.model import arrow.core.getOrElse -import com.tangem.common.ui.tokens.TokenItemStateConverter import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase @@ -68,10 +67,8 @@ internal class OnrampTokenListModel @Inject constructor( .filterByAvailability() UpdateTokenItemsTransformer( - tokenItemStateConverter = TokenItemStateConverter( - appCurrency = appCurrency, - onItemClick = params.onTokenClick, - ), + appCurrency = appCurrency, + onItemClick = params.onTokenClick, statuses = filterTokenList, isBalanceHidden = isBalanceHidden, hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(), @@ -106,14 +103,18 @@ internal class OnrampTokenListModel @Inject constructor( } } - private fun List.filterByAvailability(): List { - return filter { - when (params.filterOperation) { + private fun List.filterByAvailability(): Map> { + return groupBy { status -> + val isAvailable = when (params.filterOperation) { OnrampOperation.BUY -> { - rampStateManager.availableForBuy(scanResponse = scanResponse, cryptoCurrency = it.currency) + rampStateManager.availableForBuy(scanResponse = scanResponse, cryptoCurrency = status.currency) } - OnrampOperation.SELL -> rampStateManager.availableForSell(cryptoCurrency = it.currency) + OnrampOperation.SELL -> rampStateManager.availableForSell(cryptoCurrency = status.currency) } + + isAvailable && + status.value !is CryptoCurrencyStatus.MissedDerivation && + status.value !is CryptoCurrencyStatus.Unreachable } } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt index c75b080c3b..eab5741ead 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt @@ -4,52 +4,65 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.components.tokenlist.TokenListItem +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.ui.preview.PreviewTokenListUMProvider +import kotlinx.collections.immutable.ImmutableList /** * Token list * - * @param state state - * @param modifier modifier + * @param state state + * @param contentPadding content padding + * @param modifier modifier * [REDACTED_AUTHOR] */ @Composable internal fun TokenList(state: TokenListUM, contentPadding: PaddingValues, modifier: Modifier = Modifier) { LazyColumn(modifier = modifier, contentPadding = contentPadding) { - itemsIndexed( - items = state.items, - key = { _, item -> item.id }, - contentType = { _, item -> item::class.java }, - itemContent = { index, item -> - TokenListItem( - state = item, - isBalanceHidden = state.isBalanceHidden, - modifier = Modifier - .animateItem() - .roundedShapeItemDecoration( - currentIndex = index, - lastIndex = state.items.lastIndex, - addDefaultPadding = false, - backgroundColor = TangemTheme.colors.background.primary, - ), - ) - }, - ) + itemsBlock(items = state.availableItems, isBalanceHidden = state.isBalanceHidden) + + item { SpacerH12() } + + itemsBlock(items = state.unavailableItems, isBalanceHidden = state.isBalanceHidden) } } +private fun LazyListScope.itemsBlock(items: ImmutableList, isBalanceHidden: Boolean) { + return itemsIndexed( + items = items, + key = { _, item -> item.id }, + contentType = { _, item -> item::class.java }, + itemContent = { index, item -> + TokenListItem( + state = item, + isBalanceHidden = isBalanceHidden, + modifier = Modifier + .animateItem() + .roundedShapeItemDecoration( + currentIndex = index, + lastIndex = items.lastIndex, + addDefaultPadding = false, + backgroundColor = TangemTheme.colors.background.primary, + ), + ) + }, + ) +} + @Preview @Composable private fun Preview_TokenList(@PreviewParameter(PreviewTokenListUMProvider::class) state: TokenListUM) { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt index ce2f3e4f88..19bb331def 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt @@ -20,7 +20,7 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider Date: Sat, 2 Nov 2024 18:15:55 +0400 Subject: [PATCH 03/33] Updated on 2026-08-14 --- .../ui/tokens/TokenItemStateConverter.kt | 24 +++++++++------- .../core/ui/components/token/TokenItem.kt | 8 ++++-- .../components/token/state/TokenItemState.kt | 28 +++++++++---------- .../tokenlist/internal/NetworkTitleItem.kt | 5 +--- .../impl/model/PortfolioTokenUMConverter.kt | 2 +- .../portfolio/impl/ui/PortfolioItem.kt | 6 ++-- .../DefaultOnrampOperationComponent.kt | 2 +- .../onramp/swap/entity/ExchangeCardUM.kt | 4 +-- .../features/onramp/swap/ui/ExchangeCard.kt | 1 - .../tokenlist/OnrampTokenListComponent.kt | 3 +- .../UpdateTokenItemsTransformer.kt | 2 +- .../converter/TokenListStateConverter.kt | 4 +-- 12 files changed, 45 insertions(+), 44 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index ae4993d6ce..a4e5324a74 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -39,8 +39,8 @@ class TokenItemStateConverter( private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = { createFiatAmountState(it, appCurrency) }, - private val onItemClick: (CryptoCurrencyStatus) -> Unit, - private val onItemLongClick: ((CryptoCurrencyStatus) -> Unit)? = null, + private val onItemClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null, + private val onItemLongClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null, ) : Converter { override fun convert(value: CryptoCurrencyStatus): TokenItemState { @@ -75,9 +75,11 @@ class TokenItemStateConverter( subtitleState = requireNotNull(subtitleStateProvider(this)), fiatAmountState = requireNotNull(fiatAmountStateProvider(this)), subtitle2State = TokenItemState.Subtitle2State.TextContent(text = getFormattedAmount()), - onItemClick = { onItemClick(this) }, - onItemLongClick = onItemLongClick?.let { - { it(this) } + onItemClick = onItemClick?.let { onItemClick -> + { onItemClick(it, this) } + }, + onItemLongClick = onItemLongClick?.let { onItemLongClick -> + { onItemLongClick(it, this) } }, ) } @@ -94,9 +96,11 @@ class TokenItemStateConverter( iconState = iconStateProvider(this), titleState = titleStateProvider(this), subtitleState = subtitleStateProvider(this), - onItemClick = { onItemClick(this) }, - onItemLongClick = onItemLongClick?.let { - { it(this) } + onItemClick = onItemClick?.let { onItemClick -> + { onItemClick(it, this) } + }, + onItemLongClick = onItemLongClick?.let { onItemLongClick -> + { onItemLongClick(it, this) } }, ) } @@ -107,8 +111,8 @@ class TokenItemStateConverter( iconState = iconStateProvider(this), titleState = titleStateProvider(this), subtitleState = subtitleStateProvider(this), - onItemLongClick = onItemLongClick?.let { - { it(this) } + onItemLongClick = onItemLongClick?.let { onItemLongClick -> + { onItemLongClick(it, this) } }, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt index 3d76006795..33f3d649fc 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt @@ -145,14 +145,16 @@ private fun Modifier.tokenClickable(state: TokenItemState): Modifier = composed is TokenItemState.Unreachable, -> { val onClick = state.onItemClick - val onLongClick = state.onItemLongClick?.let { rememberHapticFeedback(state = state, onAction = it) } + val onLongClick = state.onItemLongClick?.let { + rememberHapticFeedback(state = state, onAction = { it(state) }) + } when { onClick == null && onLongClick == null -> this onClick == null && onLongClick != null -> combinedClickable(onClick = {}, onLongClick = onLongClick) - onClick != null && onLongClick == null -> combinedClickable(onClick = onClick) + onClick != null && onLongClick == null -> combinedClickable(onClick = { onClick(state) }) onClick != null && onLongClick != null -> { - combinedClickable(onClick = onClick, onLongClick = onLongClick) + combinedClickable(onClick = { onClick(state) }, onLongClick = onLongClick) } else -> this } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt index c33e5c8caa..e16a77a33b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt @@ -31,10 +31,10 @@ sealed class TokenItemState { abstract val subtitle2State: Subtitle2State? /** Callback which will be called when an item is clicked */ - abstract val onItemClick: (() -> Unit)? + abstract val onItemClick: ((TokenItemState) -> Unit)? /** Callback which will be called when an item is long clicked */ - abstract val onItemLongClick: (() -> Unit)? + abstract val onItemLongClick: ((TokenItemState) -> Unit)? /** * Loading token state @@ -52,8 +52,8 @@ sealed class TokenItemState { ) : TokenItemState() { override val fiatAmountState: FiatAmountState = FiatAmountState.Loading override val subtitle2State: Subtitle2State = Subtitle2State.Loading - override val onItemClick: (() -> Unit)? = null - override val onItemLongClick: (() -> Unit)? = null + override val onItemClick: ((TokenItemState) -> Unit)? = null + override val onItemLongClick: ((TokenItemState) -> Unit)? = null } /** @@ -67,8 +67,8 @@ sealed class TokenItemState { override val subtitleState: SubtitleState = SubtitleState.Locked override val fiatAmountState: FiatAmountState = FiatAmountState.Locked override val subtitle2State: Subtitle2State = Subtitle2State.Locked - override val onItemClick: (() -> Unit)? = null - override val onItemLongClick: (() -> Unit)? = null + override val onItemClick: ((TokenItemState) -> Unit)? = null + override val onItemLongClick: ((TokenItemState) -> Unit)? = null } /** @@ -90,8 +90,8 @@ sealed class TokenItemState { override val subtitleState: SubtitleState, override val fiatAmountState: FiatAmountState, override val subtitle2State: Subtitle2State, - override val onItemClick: (() -> Unit)?, - override val onItemLongClick: (() -> Unit)?, + override val onItemClick: ((TokenItemState) -> Unit)?, + override val onItemLongClick: ((TokenItemState) -> Unit)?, ) : TokenItemState() /** @@ -110,8 +110,8 @@ sealed class TokenItemState { ) : TokenItemState() { override val subtitleState: SubtitleState? = null override val fiatAmountState: FiatAmountState? = null - override val onItemClick: (() -> Unit)? = null - override val onItemLongClick: (() -> Unit)? = null + override val onItemClick: ((TokenItemState) -> Unit)? = null + override val onItemLongClick: ((TokenItemState) -> Unit)? = null } /** @@ -129,8 +129,8 @@ sealed class TokenItemState { override val iconState: CurrencyIconState, override val titleState: TitleState, override val subtitleState: SubtitleState? = null, - override val onItemClick: (() -> Unit)?, - override val onItemLongClick: (() -> Unit)?, + override val onItemClick: ((TokenItemState) -> Unit)?, + override val onItemLongClick: ((TokenItemState) -> Unit)?, ) : TokenItemState() { override val fiatAmountState: FiatAmountState? = null override val subtitle2State: Subtitle2State? = null @@ -150,11 +150,11 @@ sealed class TokenItemState { override val iconState: CurrencyIconState, override val titleState: TitleState, override val subtitleState: SubtitleState? = null, - override val onItemLongClick: (() -> Unit)?, + override val onItemLongClick: ((TokenItemState) -> Unit)?, ) : TokenItemState() { override val fiatAmountState: FiatAmountState? = null override val subtitle2State: Subtitle2State? = null - override val onItemClick: (() -> Unit)? = null + override val onItemClick: ((TokenItemState) -> Unit)? = null } @Immutable diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/NetworkTitleItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/NetworkTitleItem.kt index 4f6bf25949..8ca65a2982 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/NetworkTitleItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/NetworkTitleItem.kt @@ -4,7 +4,6 @@ import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -78,9 +77,7 @@ private fun BaseNetworkTitleItem( @Composable private fun DraggableIcon(reorderableTokenListState: ReorderableLazyListState) { Box( - modifier = Modifier - .size(TangemTheme.dimens.size32) - .detectReorder(reorderableTokenListState), + modifier = Modifier.detectReorder(reorderableTokenListState), contentAlignment = Alignment.Center, ) { Icon( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioTokenUMConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioTokenUMConverter.kt index 1125fda4b1..779a4293a0 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioTokenUMConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioTokenUMConverter.kt @@ -33,7 +33,7 @@ internal class PortfolioTokenUMConverter( subtitleStateProvider = { TokenItemState.SubtitleState.TextContent(value = value.status.currency.name) }, - onItemClick = onTokenItemClick, + onItemClick = { _, status -> onTokenItemClick(status) }, ) return PortfolioTokenUM( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/PortfolioItem.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/PortfolioItem.kt index e084f9e6eb..7b1d5695df 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/PortfolioItem.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/PortfolioItem.kt @@ -32,7 +32,7 @@ internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifie val onClick = state.tokenItemState.onItemClick if (onClick != null) { hapticManager.perform(TangemHapticEffect.View.ContextClick) - onClick.invoke() + onClick.invoke(it) } }, ) @@ -81,8 +81,8 @@ private fun Preview(@PreviewParameter(PortfolioTokenUMProvider::class) tokenUM: PortfolioItem( state = tokenUM.copy( tokenItemState = when (tokenUM.tokenItemState) { - is TokenItemState.Content -> tokenUM.tokenItemState.copy(onItemClick = onItemClick) - is TokenItemState.Unreachable -> tokenUM.tokenItemState.copy(onItemClick = onItemClick) + is TokenItemState.Content -> tokenUM.tokenItemState.copy(onItemClick = { onItemClick() }) + is TokenItemState.Unreachable -> tokenUM.tokenItemState.copy(onItemClick = { onItemClick() }) else -> tokenUM.tokenItemState }, isQuickActionsShown = quickActionsShown, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt index 454ba6bea6..787a5b72f9 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt @@ -35,7 +35,7 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor( filterOperation = params.operation, hasSearchBar = true, userWalletId = params.userWalletId, - onTokenClick = ::onTokenClick, + onTokenClick = { _, status -> onTokenClick(status) }, ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt index 07c60222b4..b4cef2c2bb 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt @@ -25,11 +25,9 @@ internal sealed interface ExchangeCardUM { * Empty state * * @property titleReference title reference - * @property onItemClick callback which will be called when an item is clicked */ data class Empty( override val titleReference: TextReference, - val onItemClick: () -> Unit, ) : ExchangeCardUM { override val hasRemoveButton: Boolean = false @@ -42,7 +40,7 @@ internal sealed interface ExchangeCardUM { subtitleState = TokenItemState.SubtitleState.TextContent(value = "You want to Swap"), fiatAmountState = TokenItemState.FiatAmountState.Content(text = ""), subtitle2State = TokenItemState.Subtitle2State.TextContent(text = ""), - onItemClick = onItemClick, + onItemClick = null, onItemLongClick = null, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt index 4b1ce4c245..6d4aae2867 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt @@ -101,7 +101,6 @@ private class ExchangeCardUMProvider : PreviewParameterProvider override val values: Sequence = sequenceOf( ExchangeCardUM.Empty( titleReference = resourceReference(id = R.string.swapping_from_title), - onItemClick = {}, ), ExchangeCardUM.Filled( titleReference = resourceReference(id = R.string.swapping_from_title), diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt index bcb5ab848a..cd6a67dbef 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.onramp.entity.OnrampOperation @@ -31,6 +32,6 @@ internal interface OnrampTokenListComponent { val filterOperation: OnrampOperation, val hasSearchBar: Boolean, val userWalletId: UserWalletId, - val onTokenClick: (CryptoCurrencyStatus) -> Unit, + val onTokenClick: (TokenItemState, CryptoCurrencyStatus) -> Unit, ) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt index cdf9aedfaa..03adddb707 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -19,7 +19,7 @@ import kotlinx.collections.immutable.toImmutableList @Suppress("LongParameterList") internal class UpdateTokenItemsTransformer( private val appCurrency: AppCurrency, - private val onItemClick: (CryptoCurrencyStatus) -> Unit, + private val onItemClick: (TokenItemState, CryptoCurrencyStatus) -> Unit, private val statuses: Map>, private val isBalanceHidden: Boolean, private val hasSearchBar: Boolean, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index 668118d2fb..11f5063820 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -27,8 +27,8 @@ internal class TokenListStateConverter( private val tokenStatusConverter = TokenItemStateConverter( appCurrency = appCurrency, - onItemClick = clickIntents::onTokenItemClick, - onItemLongClick = clickIntents::onTokenItemLongClick, + onItemClick = { _, status -> clickIntents.onTokenItemClick(status) }, + onItemLongClick = { _, status -> clickIntents.onTokenItemLongClick(status) }, ) override fun convert(value: WalletTokensListState): WalletTokensListState { From bc8242f423238fdd83f845c08803650130491039 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 2 Nov 2024 21:24:46 +0400 Subject: [PATCH 04/33] Updated on 2026-08-14 --- .../java/com/tangem/tap/di/ActivityModule.kt | 11 +++++++++-- .../exchangeServices/DefaultRampManager.kt | 11 ++++++++++- .../components/appbar/AppBarWithBackButton.kt | 3 +++ .../tangem/domain/exchange/RampStateManager.kt | 3 +++ .../onramp/buy/DefaultBuyCryptoComponent.kt | 3 +-- .../features/onramp/entity/OnrampOperation.kt | 1 + .../DefaultOnrampOperationComponent.kt | 17 ++++++++++------- .../selecttoken/OnrampOperationComponent.kt | 11 ++++++++--- .../onramp/sell/DefaultSellCryptoComponent.kt | 3 +-- .../transformer/UpdateTokenItemsTransformer.kt | 10 ++++++---- .../tokenlist/model/OnrampTokenListModel.kt | 6 +++++- 11 files changed, 57 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index c8d8c5c2bf..3908c30790 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -5,6 +5,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase +import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository @@ -39,8 +40,14 @@ internal object ActivityModule { @Provides @Singleton - fun provideDefaultRampManager(appStateHolder: AppStateHolder): RampStateManager { - return DefaultRampManager(appStateHolder.exchangeService) + fun provideDefaultRampManager( + appStateHolder: AppStateHolder, + marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, + ): RampStateManager { + return DefaultRampManager( + exchangeService = appStateHolder.exchangeService, + marketsCryptoCurrencyRepository = marketCryptoCurrencyRepository, + ) } @Provides diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 904e22c80e..5d6d7c5b2b 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -3,8 +3,13 @@ package com.tangem.tap.network.exchangeServices import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository +import com.tangem.domain.wallets.models.UserWalletId -class DefaultRampManager(private val exchangeService: ExchangeService?) : RampStateManager { +class DefaultRampManager( + private val exchangeService: ExchangeService?, + private val marketsCryptoCurrencyRepository: MarketCryptoCurrencyRepository, +) : RampStateManager { private val cryptoCurrencyConverter = CryptoCurrencyConverter() override fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean { @@ -19,4 +24,8 @@ class DefaultRampManager(private val exchangeService: ExchangeService?) : RampSt currency = cryptoCurrencyConverter.convertBack(cryptoCurrency), ) ?: false } + + override suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { + return marketsCryptoCurrencyRepository.isExchangeable(userWalletId, cryptoCurrency) + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index 65304bcf57..bd95803a6a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -4,6 +4,7 @@ import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM @@ -26,6 +27,7 @@ fun AppBarWithBackButton( modifier: Modifier = Modifier, text: String? = null, @DrawableRes iconRes: Int? = null, + containerColor: Color = Color.Transparent, ) { TangemTopAppBar( modifier = modifier, @@ -34,6 +36,7 @@ fun AppBarWithBackButton( iconRes = iconRes ?: R.drawable.ic_back_24, onIconClicked = onBackClick, ), + containerColor = containerColor, ) } diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index 2536d78ea9..b7a3bde7fc 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -2,6 +2,7 @@ package com.tangem.domain.exchange import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId /** * Manager that holds info about available actions as Sell and Buy @@ -11,4 +12,6 @@ interface RampStateManager { fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean + + suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt index 8e36bf09b0..7654c3c5b1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import com.tangem.core.decompose.context.AppComponentContext import com.tangem.features.onramp.component.BuyCryptoComponent -import com.tangem.features.onramp.entity.OnrampOperation import com.tangem.features.onramp.selecttoken.OnrampOperationComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -20,7 +19,7 @@ internal class DefaultBuyCryptoComponent @AssistedInject constructor( private val selectTokenComponent: OnrampOperationComponent = onrampOperationComponentFactory.create( context = appComponentContext, - params = OnrampOperationComponent.Params(operation = OnrampOperation.BUY, userWalletId = params.userWalletId), + params = OnrampOperationComponent.Params.Buy(userWalletId = params.userWalletId), ) @Composable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt index 1a94304325..54cfa7afa4 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt @@ -8,5 +8,6 @@ package com.tangem.features.onramp.entity internal enum class OnrampOperation { BUY, SELL, + SWAP, ; } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt index 787a5b72f9..da3151dadd 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt @@ -32,7 +32,10 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor( private val onrampTokenListComponent: OnrampTokenListComponent = onrampTokenListComponentFactory.create( context = child(key = "token_list"), params = OnrampTokenListComponent.Params( - filterOperation = params.operation, + filterOperation = when (params) { + is OnrampOperationComponent.Params.Buy -> OnrampOperation.BUY + is OnrampOperationComponent.Params.Sell -> OnrampOperation.SWAP + }, hasSearchBar = true, userWalletId = params.userWalletId, onTokenClick = { _, status -> onTokenClick(status) }, @@ -42,9 +45,9 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor( @Composable override fun Content(modifier: Modifier) { OnrampSelectToken( - titleResId = when (params.operation) { - OnrampOperation.BUY -> R.string.common_buy - OnrampOperation.SELL -> R.string.common_sell + titleResId = when (params) { + is OnrampOperationComponent.Params.Buy -> R.string.common_buy + is OnrampOperationComponent.Params.Sell -> R.string.common_sell }, onBackClick = router::pop, onrampTokenListComponent = onrampTokenListComponent, @@ -57,9 +60,9 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor( val appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }.code reduxStateHolder.dispatch( - when (params.operation) { - OnrampOperation.BUY -> getBuyAction(status, appCurrencyCode) - OnrampOperation.SELL -> TradeCryptoAction.Sell(status, appCurrencyCode) + action = when (params) { + is OnrampOperationComponent.Params.Buy -> getBuyAction(status, appCurrencyCode) + is OnrampOperationComponent.Params.Sell -> TradeCryptoAction.Sell(status, appCurrencyCode) }, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt index c35a452d58..74878fed83 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt @@ -3,7 +3,6 @@ package com.tangem.features.onramp.selecttoken import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.features.onramp.entity.OnrampOperation /** * Base operation component @@ -17,8 +16,14 @@ internal interface OnrampOperationComponent : ComposableContentComponent { /** * Params * - * @property operation operation * @property userWalletId id of multi-currency wallet */ - data class Params(val operation: OnrampOperation, val userWalletId: UserWalletId) + sealed interface Params { + + val userWalletId: UserWalletId + + data class Buy(override val userWalletId: UserWalletId) : Params + + data class Sell(override val userWalletId: UserWalletId) : Params + } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt index f7d63936d1..db922732cf 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import com.tangem.core.decompose.context.AppComponentContext import com.tangem.features.onramp.component.SellCryptoComponent -import com.tangem.features.onramp.entity.OnrampOperation import com.tangem.features.onramp.selecttoken.OnrampOperationComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -20,7 +19,7 @@ internal class DefaultSellCryptoComponent @AssistedInject constructor( private val selectTokenComponent: OnrampOperationComponent = onrampOperationComponentFactory.create( context = appComponentContext, - params = OnrampOperationComponent.Params(operation = OnrampOperation.SELL, userWalletId = params.userWalletId), + params = OnrampOperationComponent.Params.Sell(userWalletId = params.userWalletId), ) @Composable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt index 03adddb707..55d7d56d1c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -50,10 +50,12 @@ internal class UpdateTokenItemsTransformer( add(searchBarItem) } - createGroupTitle( - textReference = resourceReference(id = R.string.exchange_tokens_available_tokens_header), - ) - .let(::add) + if (availableItems.isNotEmpty()) { + createGroupTitle( + textReference = resourceReference(id = R.string.exchange_tokens_available_tokens_header), + ) + .let(::add) + } addAll(availableItems) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 3ca0bd1482..ed1b175497 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -103,13 +103,17 @@ internal class OnrampTokenListModel @Inject constructor( } } - private fun List.filterByAvailability(): Map> { + private suspend fun List.filterByAvailability(): Map> { return groupBy { status -> val isAvailable = when (params.filterOperation) { OnrampOperation.BUY -> { rampStateManager.availableForBuy(scanResponse = scanResponse, cryptoCurrency = status.currency) } OnrampOperation.SELL -> rampStateManager.availableForSell(cryptoCurrency = status.currency) + OnrampOperation.SWAP -> rampStateManager.availableForSwap( + userWalletId = params.userWalletId, + cryptoCurrency = status.currency, + ) } isAvailable && From edc5a0cf13323b0bbd1af04c763c590eaadd58c4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 3 Nov 2024 10:25:56 +0400 Subject: [PATCH 05/33] Updated on 2026-08-14 --- .../ui/components/tokenlist/TokenListItem.kt | 4 + .../tokenlist/internal/GroupTitleItem.kt | 31 ++++++++ .../tokenlist/state/TokensListItemUM.kt | 9 +++ .../selecttoken/ui/OnrampSelectToken.kt | 30 +++++--- .../onramp/swap/entity/ExchangeCardUM.kt | 17 ++-- .../features/onramp/swap/ui/ExchangeCard.kt | 77 +++++++++++-------- .../DefaultOnrampTokenListComponent.kt | 5 +- .../tokenlist/OnrampTokenListComponent.kt | 9 +-- .../UpdateTokenItemsTransformer.kt | 7 +- .../onramp/tokenlist/ui/OnrampTokenList.kt | 46 ++++++----- .../ui/preview/PreviewTokenListUMProvider.kt | 46 ++++++++++- 11 files changed, 188 insertions(+), 93 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt index 56b0ce8bf1..b76e11c37c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.components.token.TokenItem +import com.tangem.core.ui.components.tokenlist.internal.GroupTitleItem import com.tangem.core.ui.components.tokenlist.internal.NetworkTitleItem import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.resolveReference @@ -25,6 +26,9 @@ fun TokenListItem(state: TokensListItemUM, isBalanceHidden: Boolean, modifier: M is TokensListItemUM.NetworkGroupTitle -> { NetworkTitleItem(networkName = state.name.resolveReference(), modifier = modifier) } + is TokensListItemUM.GroupTitle -> { + GroupTitleItem(state, modifier) + } is TokensListItemUM.Token -> { TokenItem( state = state.state, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt new file mode 100644 index 0000000000..c79e40c723 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt @@ -0,0 +1,31 @@ +package com.tangem.core.ui.components.tokenlist.internal + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.ui.components.rows.NetworkTitle +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme + +/** + * Group title item + * + * @param state state + * @param modifier modifier + * +[REDACTED_AUTHOR] + */ +@Composable +fun GroupTitleItem(state: TokensListItemUM.GroupTitle, modifier: Modifier = Modifier) { + NetworkTitle( + title = { + Text( + text = state.text.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + }, + modifier = modifier, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt index 8a26f00508..2e9417d5ed 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt @@ -29,8 +29,17 @@ sealed interface TokensListItemUM { * @property id id * @property name network group name */ + @Deprecated("Use GroupTitle instead") // TODO: [REDACTED_JIRA] data class NetworkGroupTitle(override val id: Int, val name: TextReference) : TokensListItemUM + /** + * Group title + * + * @property id id + * @property text title value + */ + data class GroupTitle(override val id: Any, val text: TextReference) : TokensListItemUM + /** * Token item * diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt index 653883fb90..8d66dfa574 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt @@ -2,11 +2,12 @@ package com.tangem.features.onramp.selecttoken.ui import androidx.activity.compose.BackHandler import androidx.annotation.StringRes +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -16,6 +17,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent +@OptIn(ExperimentalFoundationApi::class) @Composable internal fun OnrampSelectToken( @StringRes titleResId: Int, @@ -25,21 +27,25 @@ internal fun OnrampSelectToken( ) { BackHandler(onBack = onBackClick) - Column( + LazyColumn( modifier = modifier .background(TangemTheme.colors.background.secondary) .imePadding() .systemBarsPadding(), ) { - AppBarWithBackButton( - onBackClick = onBackClick, - text = stringResource(id = titleResId), - iconRes = R.drawable.ic_close_24, - ) + stickyHeader(key = "header") { + AppBarWithBackButton( + onBackClick = onBackClick, + text = stringResource(id = titleResId), + iconRes = R.drawable.ic_close_24, + containerColor = TangemTheme.colors.background.secondary, + ) + } - onrampTokenListComponent.Content( - contentPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp), - modifier = Modifier, - ) + item(key = "token_list", contentType = "token_list") { + onrampTokenListComponent.Content( + modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp), + ) + } } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt index b4cef2c2bb..e427825e21 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt @@ -15,8 +15,8 @@ internal sealed interface ExchangeCardUM { /** Title reference */ val titleReference: TextReference - /** Flag that indicates if remove button should be shown */ - val hasRemoveButton: Boolean + /** Remove button UI model */ + val removeButtonUM: RemoveButtonUM? /** Token item state */ val tokenItemState: TokenItemState @@ -26,11 +26,9 @@ internal sealed interface ExchangeCardUM { * * @property titleReference title reference */ - data class Empty( - override val titleReference: TextReference, - ) : ExchangeCardUM { + data class Empty(override val titleReference: TextReference) : ExchangeCardUM { - override val hasRemoveButton: Boolean = false + override val removeButtonUM: RemoveButtonUM? = null // TODO: [REDACTED_JIRA] override val tokenItemState: TokenItemState = TokenItemState.Content( @@ -49,13 +47,14 @@ internal sealed interface ExchangeCardUM { * Filled * * @property titleReference title reference + * @property removeButtonUM remove button UI model * @property tokenItemState token item state */ data class Filled( override val titleReference: TextReference, + override val removeButtonUM: RemoveButtonUM?, override val tokenItemState: TokenItemState, - ) : ExchangeCardUM { + ) : ExchangeCardUM - override val hasRemoveButton: Boolean = true - } + data class RemoveButtonUM(val onClick: () -> Unit) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt index 6d4aae2867..92a3229445 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt @@ -1,13 +1,12 @@ package com.tangem.features.onramp.swap.ui +import android.content.res.Configuration +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.material3.ripple import androidx.compose.runtime.Composable @@ -24,6 +23,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.rows.NetworkTitle import com.tangem.core.ui.components.token.TokenItem import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme @@ -51,34 +51,41 @@ internal fun ExchangeCard(state: ExchangeCardUM, modifier: Modifier = Modifier) ), verticalArrangement = Arrangement.SpaceBetween, ) { - NetworkTitle( - title = { - Text( - text = state.titleReference.resolveReference(), - color = TangemTheme.colors.text.tertiary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = TangemTheme.typography.subtitle2, - ) - }, - action = { - RemoveButton(hasRemoveButton = state.hasRemoveButton, onClick = {}) - }, - ) + Title(titleReference = state.titleReference, removeButtonUM = state.removeButtonUM) - TokenItem(state = state.tokenItemState, isBalanceHidden = false) + AnimatedContent(targetState = state.tokenItemState, label = "TokenItem's changing ") { + TokenItem(state = it, isBalanceHidden = false) + } } } @Composable -private fun RemoveButton(hasRemoveButton: Boolean, onClick: () -> Unit) { - AnimatedVisibility(visible = hasRemoveButton) { +private fun Title(titleReference: TextReference, removeButtonUM: ExchangeCardUM.RemoveButtonUM?) { + NetworkTitle( + title = { + Text( + text = titleReference.resolveReference(), + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = TangemTheme.typography.subtitle2, + ) + }, + action = { RemoveButton(state = removeButtonUM) }, + ) +} + +@Composable +private fun RemoveButton(state: ExchangeCardUM.RemoveButtonUM?) { + AnimatedVisibility(visible = state != null) { + state ?: return@AnimatedVisibility + Text( text = stringResource(id = R.string.manage_tokens_remove), modifier = Modifier.clickable( indication = ripple(bounded = false), interactionSource = remember { MutableInteractionSource() }, - onClick = onClick, + onClick = state.onClick, ), color = TangemTheme.colors.text.accent, maxLines = 1, @@ -88,22 +95,32 @@ private fun RemoveButton(hasRemoveButton: Boolean, onClick: () -> Unit) { } } -@Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview_ExchangeCard(@PreviewParameter(ExchangeCardUMProvider::class) state: ExchangeCardUM) { TangemThemePreview { - ExchangeCard(state = state) + ExchangeCard( + state = state, + modifier = Modifier + .background(TangemTheme.colors.background.secondary) + .padding(16.dp), + ) } } private class ExchangeCardUMProvider : PreviewParameterProvider { override val values: Sequence = sequenceOf( - ExchangeCardUM.Empty( - titleReference = resourceReference(id = R.string.swapping_from_title), - ), - ExchangeCardUM.Filled( + ExchangeCardUM.Empty(titleReference = resourceReference(id = R.string.swapping_from_title)), + createFilled(removeButtonUM = null), + createFilled(removeButtonUM = ExchangeCardUM.RemoveButtonUM { }), + ) + + private fun createFilled(removeButtonUM: ExchangeCardUM.RemoveButtonUM?): ExchangeCardUM.Filled { + return ExchangeCardUM.Filled( titleReference = resourceReference(id = R.string.swapping_from_title), + removeButtonUM = removeButtonUM, tokenItemState = TokenItemState.Content( id = "1", iconState = CurrencyIconState.Locked, @@ -118,6 +135,6 @@ private class ExchangeCardUMProvider : PreviewParameterProvider onItemClick = {}, onItemLongClick = {}, ), - ), - ) + ) + } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/DefaultOnrampTokenListComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/DefaultOnrampTokenListComponent.kt index 67ada9b94b..c538d475cd 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/DefaultOnrampTokenListComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/DefaultOnrampTokenListComponent.kt @@ -1,6 +1,5 @@ package com.tangem.features.onramp.tokenlist -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue @@ -23,10 +22,10 @@ internal class DefaultOnrampTokenListComponent @AssistedInject constructor( private val model: OnrampTokenListModel = getOrCreateModel(params) @Composable - override fun Content(contentPadding: PaddingValues, modifier: Modifier) { + override fun Content(modifier: Modifier) { val state by model.state.collectAsStateWithLifecycle() - TokenList(state = state, contentPadding = contentPadding, modifier = modifier) + TokenList(state = state, modifier = modifier) } @AssistedFactory diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt index cd6a67dbef..6cea4a5b28 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt @@ -1,21 +1,16 @@ package com.tangem.features.onramp.tokenlist -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import androidx.compose.ui.Modifier import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.onramp.entity.OnrampOperation /** Token list component that present list of token for multi-currency wallet */ @Stable -internal interface OnrampTokenListComponent { - - @Composable - fun Content(contentPadding: PaddingValues, modifier: Modifier) +internal interface OnrampTokenListComponent : ComposableContentComponent { /** Component factory */ interface Factory : ComponentFactory diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt index 55d7d56d1c..a4eed528a1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -133,10 +133,7 @@ internal class UpdateTokenItemsTransformer( ) } - private fun createGroupTitle(textReference: TextReference): TokensListItemUM.NetworkGroupTitle { - return TokensListItemUM.NetworkGroupTitle( - id = textReference.hashCode(), - name = textReference, - ) + private fun createGroupTitle(textReference: TextReference): TokensListItemUM.GroupTitle { + return TokensListItemUM.GroupTitle(id = textReference.hashCode(), text = textReference) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt index eab5741ead..807100bf6a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt @@ -1,16 +1,17 @@ package com.tangem.features.onramp.tokenlist.ui +import android.content.res.Configuration import androidx.compose.foundation.background -import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.key import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEachIndexed import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.components.tokenlist.TokenListItem import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM @@ -24,34 +25,30 @@ import kotlinx.collections.immutable.ImmutableList /** * Token list * - * @param state state - * @param contentPadding content padding - * @param modifier modifier + * @param state state + * @param modifier modifier * [REDACTED_AUTHOR] */ @Composable -internal fun TokenList(state: TokenListUM, contentPadding: PaddingValues, modifier: Modifier = Modifier) { - LazyColumn(modifier = modifier, contentPadding = contentPadding) { - itemsBlock(items = state.availableItems, isBalanceHidden = state.isBalanceHidden) +internal fun TokenList(state: TokenListUM, modifier: Modifier = Modifier) { + Column(modifier) { + ItemsBlock(items = state.availableItems, isBalanceHidden = state.isBalanceHidden) - item { SpacerH12() } + SpacerH12() - itemsBlock(items = state.unavailableItems, isBalanceHidden = state.isBalanceHidden) + ItemsBlock(items = state.unavailableItems, isBalanceHidden = state.isBalanceHidden) } } -private fun LazyListScope.itemsBlock(items: ImmutableList, isBalanceHidden: Boolean) { - return itemsIndexed( - items = items, - key = { _, item -> item.id }, - contentType = { _, item -> item::class.java }, - itemContent = { index, item -> +@Composable +private fun ItemsBlock(items: ImmutableList, isBalanceHidden: Boolean) { + items.fastForEachIndexed { index, item -> + key(item.id) { TokenListItem( state = item, isBalanceHidden = isBalanceHidden, modifier = Modifier - .animateItem() .roundedShapeItemDecoration( currentIndex = index, lastIndex = items.lastIndex, @@ -59,20 +56,21 @@ private fun LazyListScope.itemsBlock(items: ImmutableList, isB backgroundColor = TangemTheme.colors.background.primary, ), ) - }, - ) + } + } } -@Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun Preview_TokenList(@PreviewParameter(PreviewTokenListUMProvider::class) state: TokenListUM) { TangemThemePreview { TokenList( state = state, - contentPadding = PaddingValues(all = 16.dp), modifier = Modifier .fillMaxWidth() - .background(color = TangemTheme.colors.background.secondary), + .background(color = TangemTheme.colors.background.secondary) + .padding(16.dp), ) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt index 19bb331def..273cad4fd0 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt @@ -9,13 +9,24 @@ import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList internal class PreviewTokenListUMProvider : PreviewParameterProvider { override val values: Sequence = sequenceOf( - createTokensList(hasSearchBar = false, createDefaultTokenItem()), - createTokensList(hasSearchBar = true, createDefaultTokenItem()), + createTokensList( + hasSearchBar = false, + createHeader(), + createDefaultTokenItem(), + createDefaultTokenItem(), + ), + createTokensList( + hasSearchBar = true, + createHeader(), + createDefaultTokenItem(), + createDefaultTokenItem(), + ), ) private fun createTokensList(hasSearchBar: Boolean, vararg items: TokensListItemUM): TokenListUM { @@ -37,11 +48,22 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider Date: Sun, 3 Nov 2024 17:56:53 +0400 Subject: [PATCH 06/33] Updated on 2026-08-14 --- .../component/SwapSelectTokensComponent.kt | 22 ++++ .../swap/DefaultSwapSelectTokensComponent.kt | 68 ++++++++++++ .../di/SwapSelectTokensComponentModule.kt | 20 ++++ .../swap/di/SwapSelectTokensModelModule.kt | 20 ++++ .../swap/entity/SwapSelectTokensController.kt | 42 ++++++++ .../onramp/swap/entity/SwapSelectTokensUM.kt | 16 +++ .../entity/SwapSelectTokensUMTransformer.kt | 10 ++ .../RemoveSelectedFromTokenTransformer.kt | 20 ++++ .../transformer/SelectFromTokenTransformer.kt | 30 ++++++ .../transformer/SelectToTokenTransformer.kt | 30 ++++++ .../swap/entity/utils/ExchangeCardUMExt.kt | 21 ++++ .../swap/model/SwapSelectTokensModel.kt | 62 +++++++++++ .../onramp/swap/ui/SwapSelectTokens.kt | 100 ++++++++++++++++++ 13 files changed, 461 insertions(+) create mode 100644 features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SwapSelectTokensComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensComponentModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUMTransformer.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectFromTokenTransformer.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectToTokenTransformer.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SwapSelectTokensComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SwapSelectTokensComponent.kt new file mode 100644 index 0000000000..2e28ee5552 --- /dev/null +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SwapSelectTokensComponent.kt @@ -0,0 +1,22 @@ +package com.tangem.features.onramp.component + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Swap select tokens component + * +[REDACTED_AUTHOR] + */ +interface SwapSelectTokensComponent : ComposableContentComponent { + + interface Factory : ComponentFactory + + /** + * Params + * + * @property userWalletId user wallet id + */ + data class Params(val userWalletId: UserWalletId) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt new file mode 100644 index 0000000000..fdcecebdc0 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt @@ -0,0 +1,68 @@ +package com.tangem.features.onramp.swap + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.features.onramp.component.SwapSelectTokensComponent +import com.tangem.features.onramp.entity.OnrampOperation +import com.tangem.features.onramp.swap.model.SwapSelectTokensModel +import com.tangem.features.onramp.swap.ui.SwapSelectTokens +import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +@Stable +internal class DefaultSwapSelectTokensComponent @AssistedInject constructor( + tokenListComponentFactory: OnrampTokenListComponent.Factory, + @Assisted private val appComponentContext: AppComponentContext, + @Assisted private val params: SwapSelectTokensComponent.Params, +) : AppComponentContext by appComponentContext, SwapSelectTokensComponent { + + private val model: SwapSelectTokensModel = getOrCreateModel() + + private val selectFromTokenListComponent: OnrampTokenListComponent = tokenListComponentFactory.create( + context = child(key = "select_from_token_list"), + params = OnrampTokenListComponent.Params( + filterOperation = OnrampOperation.SWAP, + hasSearchBar = true, + userWalletId = params.userWalletId, + onTokenClick = model::selectFromToken, + ), + ) + + private val selectToTokenListComponent: OnrampTokenListComponent = tokenListComponentFactory.create( + context = child(key = "select_to_token_list"), + params = OnrampTokenListComponent.Params( + filterOperation = OnrampOperation.SWAP, + hasSearchBar = true, + userWalletId = params.userWalletId, + onTokenClick = model::selectToToken, + ), + ) + + @Composable + override fun Content(modifier: Modifier) { + val state = model.state.collectAsStateWithLifecycle() + + SwapSelectTokens( + state = state.value, + selectFromTokenListComponent = selectFromTokenListComponent, + selectToTokenListComponent = selectToTokenListComponent, + modifier = modifier, + ) + } + + @AssistedFactory + interface Factory : SwapSelectTokensComponent.Factory { + + override fun create( + context: AppComponentContext, + params: SwapSelectTokensComponent.Params, + ): DefaultSwapSelectTokensComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensComponentModule.kt new file mode 100644 index 0000000000..059c28a82a --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.swap.di + +import com.tangem.features.onramp.component.SwapSelectTokensComponent +import com.tangem.features.onramp.swap.DefaultSwapSelectTokensComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface SwapSelectTokensComponentModule { + + @Binds + @Singleton + fun bindSwapSelectTokensComponentFactory( + factory: DefaultSwapSelectTokensComponent.Factory, + ): SwapSelectTokensComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt new file mode 100644 index 0000000000..f7e0741156 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.swap.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.swap.model.SwapSelectTokensModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface SwapSelectTokensModelModule { + + @Binds + @IntoMap + @ClassKey(SwapSelectTokensModel::class) + fun bindOnrampTokenListModel(model: SwapSelectTokensModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt new file mode 100644 index 0000000000..64fac02313 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt @@ -0,0 +1,42 @@ +package com.tangem.features.onramp.swap.entity + +import com.tangem.core.decompose.navigation.Router +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onramp.impl.R +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import timber.log.Timber +import javax.inject.Inject + +/** + * [SwapSelectTokensUM] controller + * + * @property router router + * +[REDACTED_AUTHOR] + */ +internal class SwapSelectTokensController @Inject constructor( + private val router: Router, +) { + + val state: StateFlow get() = _state + + private val _state: MutableStateFlow = MutableStateFlow( + value = SwapSelectTokensUM( + onBackClick = router::pop, + exchangeFrom = ExchangeCardUM.Empty(titleReference = resourceReference(id = R.string.swapping_from_title)), + exchangeTo = ExchangeCardUM.Empty(titleReference = resourceReference(id = R.string.swapping_to_title)), + ), + ) + + fun update(transform: (SwapSelectTokensUM) -> SwapSelectTokensUM) { + Timber.d("Applying non-name transformation") + _state.update(transform) + } + + fun update(transformer: SwapSelectTokensUMTransformer) { + Timber.d("Applying ${transformer::class.simpleName}") + _state.update(transformer::transform) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUM.kt new file mode 100644 index 0000000000..7defa5ca8f --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUM.kt @@ -0,0 +1,16 @@ +package com.tangem.features.onramp.swap.entity + +/** + * Swap select tokens UI model + * + * @property onBackClick callback is called when back button is clicked + * @property exchangeFrom exchange "from" card UI model + * @property exchangeTo exchange "to" card UI model + * +[REDACTED_AUTHOR] + */ +internal data class SwapSelectTokensUM( + val onBackClick: () -> Unit, + val exchangeFrom: ExchangeCardUM, + val exchangeTo: ExchangeCardUM, +) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUMTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUMTransformer.kt new file mode 100644 index 0000000000..fb2f9ca537 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUMTransformer.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onramp.swap.entity + +import com.tangem.utils.transformer.Transformer + +/** + * Base [SwapSelectTokensUM] transformer + * +[REDACTED_AUTHOR] + */ +internal interface SwapSelectTokensUMTransformer : Transformer \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt new file mode 100644 index 0000000000..52d581ddf2 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.swap.entity.transformer + +import com.tangem.features.onramp.swap.entity.ExchangeCardUM +import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM +import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer + +/** + * Transformer for removing selected "from" token + * +[REDACTED_AUTHOR] + */ +internal object RemoveSelectedFromTokenTransformer : SwapSelectTokensUMTransformer { + + override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM { + return prevState.copy( + exchangeFrom = ExchangeCardUM.Empty(titleReference = prevState.exchangeFrom.titleReference), + exchangeTo = ExchangeCardUM.Empty(titleReference = prevState.exchangeTo.titleReference), + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectFromTokenTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectFromTokenTransformer.kt new file mode 100644 index 0000000000..08c088f59e --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectFromTokenTransformer.kt @@ -0,0 +1,30 @@ +package com.tangem.features.onramp.swap.entity.transformer + +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.features.onramp.swap.entity.ExchangeCardUM +import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM +import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer +import com.tangem.features.onramp.swap.entity.utils.toFilled + +/** + * Transformer for selecting "from" token + * + * @property selectedTokenItemState token item state + * @property onRemoveClick callback is called when remove button is clicked + * +[REDACTED_AUTHOR] + */ +internal class SelectFromTokenTransformer( + private val selectedTokenItemState: TokenItemState, + private val onRemoveClick: () -> Unit, +) : SwapSelectTokensUMTransformer { + + override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM { + return prevState.copy( + exchangeFrom = prevState.exchangeFrom.toFilled( + selectedTokenItemState = selectedTokenItemState, + removeButtonUM = ExchangeCardUM.RemoveButtonUM(onClick = onRemoveClick), + ), + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectToTokenTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectToTokenTransformer.kt new file mode 100644 index 0000000000..433b28f5ff --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectToTokenTransformer.kt @@ -0,0 +1,30 @@ +package com.tangem.features.onramp.swap.entity.transformer + +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.features.onramp.swap.entity.ExchangeCardUM +import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM +import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer +import com.tangem.features.onramp.swap.entity.utils.toFilled + +/** + * Transformer for selecting "to" token + * + * @property selectedTokenItemState token item state + * +[REDACTED_AUTHOR] + */ +internal class SelectToTokenTransformer( + private val selectedTokenItemState: TokenItemState, +) : SwapSelectTokensUMTransformer { + + override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM { + return prevState.copy( + exchangeFrom = prevState.exchangeFrom.hideRemoveButton(), + exchangeTo = prevState.exchangeTo.toFilled(selectedTokenItemState = selectedTokenItemState), + ) + } + + private fun ExchangeCardUM.hideRemoveButton(): ExchangeCardUM { + return (this as? ExchangeCardUM.Filled)?.copy(removeButtonUM = null) ?: this + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt new file mode 100644 index 0000000000..5e6a03ea40 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt @@ -0,0 +1,21 @@ +package com.tangem.features.onramp.swap.entity.utils + +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.features.onramp.swap.entity.ExchangeCardUM + +/** + * Convert from [ExchangeCardUM] to [ExchangeCardUM.Filled] + * + * @param selectedTokenItemState token item state + * @param removeButtonUM remove button UI model + */ +internal fun ExchangeCardUM.toFilled( + selectedTokenItemState: TokenItemState, + removeButtonUM: ExchangeCardUM.RemoveButtonUM? = null, +): ExchangeCardUM.Filled { + return ExchangeCardUM.Filled( + titleReference = titleReference, + tokenItemState = selectedTokenItemState, + removeButtonUM = removeButtonUM, + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt new file mode 100644 index 0000000000..858f97a8c0 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt @@ -0,0 +1,62 @@ +package com.tangem.features.onramp.swap.model + +import com.tangem.core.decompose.model.Model +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.onramp.swap.entity.SwapSelectTokensController +import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM +import com.tangem.features.onramp.swap.entity.transformer.RemoveSelectedFromTokenTransformer +import com.tangem.features.onramp.swap.entity.transformer.SelectFromTokenTransformer +import com.tangem.features.onramp.swap.entity.transformer.SelectToTokenTransformer +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@Suppress("LongParameterList") +internal class SwapSelectTokensModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val controller: SwapSelectTokensController, +) : Model() { + + val state: StateFlow = controller.state + + private var fromCurrencyStatus: CryptoCurrencyStatus? = null + private var toCurrencyStatus: CryptoCurrencyStatus? = null + + /** + * Select "from" token + * + * @param selectedTokenItemState selected token item state + * @param status crypto currency status + */ + fun selectFromToken(selectedTokenItemState: TokenItemState, status: CryptoCurrencyStatus) { + fromCurrencyStatus = status + + controller.update( + transformer = SelectFromTokenTransformer( + selectedTokenItemState = selectedTokenItemState, + onRemoveClick = ::onRemoveClick, + ), + ) + } + + /** + * Select "to" token + * + * @param selectedTokenItemState selected token item state + * @param status crypto currency status + */ + fun selectToToken(selectedTokenItemState: TokenItemState, status: CryptoCurrencyStatus) { + toCurrencyStatus = status + + controller.update(transformer = SelectToTokenTransformer(selectedTokenItemState)) + + // TODO: [REDACTED_JIRA] + } + + private fun onRemoveClick() { + fromCurrencyStatus = null + + controller.update(transformer = RemoveSelectedFromTokenTransformer) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt new file mode 100644 index 0000000000..3179099e4d --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt @@ -0,0 +1,100 @@ +package com.tangem.features.onramp.swap.ui + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.swap.entity.ExchangeCardUM +import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM +import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent + +/** + * Swap select tokens + * + * @param state state + * @param selectFromTokenListComponent select "from" token list component + * @param selectToTokenListComponent select "to" token list component + * @param modifier modifier + * +[REDACTED_AUTHOR] + */ +@OptIn(ExperimentalFoundationApi::class) +@Composable +internal fun SwapSelectTokens( + state: SwapSelectTokensUM, + selectFromTokenListComponent: OnrampTokenListComponent, + selectToTokenListComponent: OnrampTokenListComponent, + modifier: Modifier = Modifier, +) { + BackHandler(onBack = state.onBackClick) + + LazyColumn( + modifier = modifier + .background(TangemTheme.colors.background.secondary) + .imePadding() + .systemBarsPadding(), + contentPadding = PaddingValues(bottom = 8.dp), + ) { + stickyHeader(key = "header") { + AppBarWithBackButton( + onBackClick = state.onBackClick, + text = stringResource(id = R.string.common_swap), + iconRes = R.drawable.ic_close_24, + containerColor = TangemTheme.colors.background.secondary, + ) + } + + item(key = "exchange_from", contentType = "exchange_from") { + ExchangeCard( + state = state.exchangeFrom, + modifier = Modifier + .padding(horizontal = 16.dp) + .padding(top = 8.dp, bottom = 12.dp) + .animateItem(), + ) + } + + if (state.exchangeFrom is ExchangeCardUM.Empty) { + item(key = "select_from", contentType = "select_from") { + selectFromTokenListComponent.Content( + modifier = Modifier + .padding(horizontal = 16.dp) + .animateItem(), + ) + } + } + + if (state.exchangeFrom is ExchangeCardUM.Filled) { + item(key = "exchange_to", contentType = "exchange_to") { + ExchangeCard( + state = state.exchangeTo, + modifier = Modifier + .padding(horizontal = 16.dp) + .padding(bottom = 12.dp) + .animateItem(), + ) + } + + if (state.exchangeTo is ExchangeCardUM.Empty) { + item(key = "select_to", contentType = "select_to") { + selectToTokenListComponent.Content( + modifier = Modifier + .padding(horizontal = 16.dp) + .animateItem(), + ) + } + } + } + } +} \ No newline at end of file From 9597f49fedcd32fc1e7a4c2cacc32650f6f19e7b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 10:23:39 +0300 Subject: [PATCH 07/33] Updated on 2026-08-14 --- .../impl/DefaultOnboardingEntryComponent.kt | 8 ++ .../DefaultOnboardingMultiWalletComponent.kt | 78 +++++++++++-- .../impl/child/MultiWalletChildComponent.kt | 5 + .../impl/child/MultiWalletChildParams.kt | 10 ++ .../backup/MultiWalletBackupComponent.kt | 40 +++++++ .../backup/model/MultiWalletBackupModel.kt | 31 +++++ .../impl/child/backup/ui/MultiWalletBackup.kt | 73 ++++++++++++ .../backup/ui/state/MultiWalletBackupUM.kt | 7 ++ .../MultiWalletCreateWalletComponent.kt | 44 +++++++ .../impl/child/createwallet/model/Dialogs.kt | 16 +++ .../model/MultiWalletCreateWalletModel.kt | 108 ++++++++++++++++++ .../ui/MultiWalletCreateWallet.kt | 92 +++++++++++++++ .../ui/state/MultiWalletCreateWalletUM.kt | 20 ++++ .../v2/multiwallet/impl/di/ComponentModule.kt | 12 ++ .../impl/model/OnboardingMultiWalletModel.kt | 84 +------------- .../impl/model/OnboardingMultiWalletState.kt | 5 +- .../v2/multiwallet/impl/model/Utils.kt | 7 +- .../impl/DefaultOnboardingStepperComponent.kt | 13 ++- 18 files changed, 556 insertions(+), 97 deletions(-) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildComponent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildParams.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt index ac06b7c4a5..623f315db0 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt @@ -105,6 +105,14 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor( }.saveIn(innerNavigationLinkJobHolder) } } + + componentScope.launch { + model.titleProvider.currentTitle.collect { title -> + stepperComponent.state.update { + it.copy(title = title) + } + } + } } @Composable diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt index 8cc7a9e553..8fbb204794 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt @@ -3,14 +3,24 @@ package com.tangem.features.onboarding.v2.multiwallet.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children +import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation +import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.router.stack.* +import com.arkivanov.decompose.value.Value import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.navigation.inner.InnerNavigation import com.tangem.core.decompose.navigation.inner.InnerNavigationState import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams +import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWalletBackupComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel -import com.tangem.features.onboarding.v2.multiwallet.impl.ui.OnboardingMultiWallet +import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -23,24 +33,74 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor private val model: OnboardingMultiWalletModel = getOrCreateModel(params) + private val stackNavigation = StackNavigation() + override val innerNavigation: InnerNavigation = object : InnerNavigation { override val state = MutableStateFlow( - Wallet12InnerNavigationState(1, 5), // TODO + Wallet12InnerNavigationState(1, 5), ) override fun pop(onComplete: (Boolean) -> Unit) { - // TODO + // TODO add warning dialog + stackNavigation.pop(onComplete) + } + } + + private val childStack: Value> = childStack( + key = "innerStack", + source = stackNavigation, + serializer = null, + initialConfiguration = model.state.value.currentStep, + handleBackButton = true, + childFactory = { configuration, factoryContext -> createChild(configuration, childByContext(factoryContext)) }, + ) + + private fun createChild( + step: OnboardingMultiWalletState.Step, + childContext: AppComponentContext, + ): MultiWalletChildComponent { + return when (step) { + OnboardingMultiWalletState.Step.CreateWallet -> MultiWalletCreateWalletComponent( + context = childContext, + params = MultiWalletChildParams( + multiWalletState = model.state, + parentParams = params, + ), + onDone = ::onStepDone, + ) + OnboardingMultiWalletState.Step.AddBackupDevice -> MultiWalletBackupComponent( + context = childContext, + params = MultiWalletChildParams( + multiWalletState = model.state, + parentParams = params, + ), + onDone = ::onStepDone, + ) + OnboardingMultiWalletState.Step.FinishBackup -> TODO() + OnboardingMultiWalletState.Step.Done -> TODO() + } + } + + private fun onStepDone() { + when (model.state.value.currentStep) { + OnboardingMultiWalletState.Step.CreateWallet -> + stackNavigation.push(OnboardingMultiWalletState.Step.AddBackupDevice) + OnboardingMultiWalletState.Step.AddBackupDevice -> TODO() + OnboardingMultiWalletState.Step.FinishBackup -> TODO() + OnboardingMultiWalletState.Step.Done -> TODO() } } @Composable override fun Content(modifier: Modifier) { - val uiState by model.uiState.collectAsStateWithLifecycle() + val stackState by childStack.subscribeAsState() - OnboardingMultiWallet( - modifier = modifier, - state = uiState, - ) + Children( + stack = stackState, + animation = stackAnimation(slide()), + ) { + it.instance.Content(Modifier) + } } @AssistedFactory diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildComponent.kt new file mode 100644 index 0000000000..c484f28a70 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildComponent.kt @@ -0,0 +1,5 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child + +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface MultiWalletChildComponent : ComposableContentComponent \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildParams.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildParams.kt new file mode 100644 index 0000000000..3e2d3bffb3 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildParams.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child + +import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState +import kotlinx.coroutines.flow.MutableStateFlow + +class MultiWalletChildParams( + val multiWalletState: MutableStateFlow, + val parentParams: OnboardingMultiWalletComponent.Params, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt new file mode 100644 index 0000000000..ff0422eeab --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt @@ -0,0 +1,40 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup + +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.resourceReference +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.MultiWalletBackupModel +import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.MultiWalletBackup + +@Suppress("UnusedPrivateMember") +class MultiWalletBackupComponent( + context: AppComponentContext, + params: MultiWalletChildParams, + onDone: () -> Unit, +) : AppComponentContext by context, MultiWalletChildComponent { + + private val model: MultiWalletBackupModel = getOrCreateModel(params) + + init { + params.parentParams.titleProvider.changeTitle( + text = resourceReference(R.string.onboarding_navbar_title_creating_backup), + ) + } + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + + MultiWalletBackup( + modifier = modifier, + state = state, + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt new file mode 100644 index 0000000000..1b6ddac97b --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt @@ -0,0 +1,31 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model + +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams +import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ComponentScoped +class MultiWalletBackupModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, +) : Model() { + + @Suppress("UnusedPrivateMember") + private val params = paramsContainer.require() + + private val _uiState = MutableStateFlow( + MultiWalletBackupUM( + finalizeButtonEnabled = false, + onAddBackupClick = {}, + onFinalizeButtonClick = {}, + ), + ) + + val uiState: StateFlow = _uiState +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt new file mode 100644 index 0000000000..d050a2128f --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt @@ -0,0 +1,73 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUM + +@Composable +fun MultiWalletBackup(state: MultiWalletBackupUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxSize() + .navigationBarsPadding() + .verticalScroll(rememberScrollState()), + ) { + Box( + Modifier + .fillMaxWidth() + .weight(1f), + ) { + Box( + Modifier + .align(Alignment.Center) + .size(100.dp) + .background(Color.Black), + ) + } + + PrimaryButtonIconEnd( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) + .fillMaxWidth(), + iconResId = R.drawable.ic_tangem_24, + text = stringResource(R.string.onboarding_button_add_backup_card), + onClick = state.onAddBackupClick, + ) + + SecondaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + enabled = state.finalizeButtonEnabled, + text = stringResource(R.string.onboarding_button_finalize_backup), + onClick = state.onFinalizeButtonClick, + ) + } +} + +@Preview +@Composable +private fun Preview() { + TangemThemePreview { + MultiWalletBackup( + state = MultiWalletBackupUM( + finalizeButtonEnabled = true, + onAddBackupClick = {}, + onFinalizeButtonClick = {}, + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt new file mode 100644 index 0000000000..013235cb7b --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt @@ -0,0 +1,7 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state + +data class MultiWalletBackupUM( + val finalizeButtonEnabled: Boolean, + val onAddBackupClick: () -> Unit, + val onFinalizeButtonClick: () -> Unit, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt new file mode 100644 index 0000000000..713eb54a50 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt @@ -0,0 +1,44 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet + +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.resourceReference +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model.MultiWalletCreateWalletModel +import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.MultiWalletCreateWallet +import kotlinx.coroutines.launch + +class MultiWalletCreateWalletComponent( + context: AppComponentContext, + params: MultiWalletChildParams, + onDone: () -> Unit, +) : AppComponentContext by context, MultiWalletChildComponent { + + private val model: MultiWalletCreateWalletModel = getOrCreateModel(params) + + init { + params.parentParams.titleProvider.changeTitle( + text = resourceReference(R.string.onboarding_create_wallet_header), + ) + + componentScope.launch { + model.onDone.collect { onDone() } + } + } + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + + MultiWalletCreateWallet( + modifier = modifier, + state = state, + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt new file mode 100644 index 0000000000..4dcd0f1696 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt @@ -0,0 +1,16 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model + +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM + +internal fun resetCardDialog(onConfirm: () -> Unit, onDismiss: () -> Unit, onDismissButtonClick: () -> Unit) = + MultiWalletCreateWalletUM.Dialog( + title = resourceReference(R.string.onboarding_activation_error_title), + description = resourceReference(R.string.onboarding_activation_error_message), + confirmButtonText = resourceReference(R.string.common_ok), + dismissButtonText = resourceReference(R.string.common_support), + onDismiss = onDismiss, + onConfirm = onConfirm, + onDismissButtonClick = onDismissButtonClick, + ) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt new file mode 100644 index 0000000000..0a1992053f --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt @@ -0,0 +1,108 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model + +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.feedback.GetCardInfoUseCase +import com.tangem.domain.feedback.SendFeedbackEmailUseCase +import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams +import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM +import com.tangem.sdk.api.TangemSdkManager +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ComponentScoped +class MultiWalletCreateWalletModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val tangemSdkManager: TangemSdkManager, + private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, + private val getCardInfoUseCase: GetCardInfoUseCase, +) : Model() { + + private val params = paramsContainer.require() + private val multiWalletState + get() = params.multiWalletState + + private val _uiState = MutableStateFlow( + MultiWalletCreateWalletUM( + onCreateWalletClick = { createWallet(false) }, + showSeedPhraseOption = params.parentParams.withSeedPhraseFlow, + onOtherOptionsClick = { /* navigate */ }, + dialog = null, + ), + ) + + val uiState: StateFlow = _uiState + val onDone = MutableSharedFlow() + + private fun createWallet(shouldReset: Boolean) { + modelScope.launch { + val result = tangemSdkManager.createProductWallet( + scanResponse = multiWalletState.value.currentScanResponse, + shouldReset = shouldReset, + ) + + when (result) { + is CompletionResult.Success -> { + multiWalletState.update { + it.copy( + currentScanResponse = it.currentScanResponse.copy( + card = result.data.card, + derivedKeys = result.data.derivedKeys, + primaryCard = result.data.primaryCard, + ), + ) + } + + onDone.emit(Unit) + // TODO + // Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) + } + + is CompletionResult.Failure -> { + if (result.error is TangemSdkError.WalletAlreadyCreated) { + // show should reset dialog + handleActivationError() + } + } + } + } + } + + private fun handleActivationError() { + _uiState.update { state -> + state.copy( + dialog = resetCardDialog( + onConfirm = { + _uiState.update { it.copy(dialog = null) } + resetCard() + }, + onDismiss = { + _uiState.update { it.copy(dialog = null) } + }, + onDismissButtonClick = ::navigateToSupportScreen, + ), + ) + } + } + + private fun resetCard() { + createWallet(true) + } + + fun navigateToSupportScreen() { + modelScope.launch { + val cardInfo = getCardInfoUseCase(multiWalletState.value.currentScanResponse).getOrNull() ?: return@launch + sendFeedbackEmailUseCase(FeedbackEmailType.DirectUserRequest(cardInfo)) + } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt new file mode 100644 index 0000000000..6b683f4ce6 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt @@ -0,0 +1,92 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.BasicDialog +import com.tangem.core.ui.components.DialogButtonUM +import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM + +@Composable +fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier = Modifier) { + if (state.dialog != null) { + BasicDialog( + title = state.dialog.title.resolveReference(), + message = state.dialog.description.resolveReference(), + confirmButton = DialogButtonUM( + title = state.dialog.confirmButtonText.resolveReference(), + onClick = state.dialog.onConfirm, + ), + dismissButton = DialogButtonUM( + title = state.dialog.dismissButtonText.resolveReference(), + onClick = state.dialog.onDismissButtonClick, + ), + onDismissDialog = state.dialog.onDismiss, + ) + } + + Column( + modifier = modifier + .fillMaxSize() + .navigationBarsPadding() + .verticalScroll(rememberScrollState()), + ) { + Box( + Modifier + .fillMaxWidth() + .weight(1f), + ) { + Box( + Modifier + .align(Alignment.Center) + .size(100.dp) + .background(Color.Black), + ) + } + + PrimaryButtonIconEnd( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) + .fillMaxWidth(), + iconResId = R.drawable.ic_tangem_24, + text = stringResource(R.string.onboarding_create_wallet_button_create_wallet), + onClick = state.onCreateWalletClick, + ) + + SecondaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = stringResource(R.string.onboarding_create_wallet_options_button_options), + onClick = state.onOtherOptionsClick, + ) + } +} + +@Preview +@Composable +private fun Preview() { + TangemThemePreview { + MultiWalletCreateWallet( + state = MultiWalletCreateWalletUM( + onCreateWalletClick = {}, + showSeedPhraseOption = true, + onOtherOptionsClick = {}, + dialog = null, + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt new file mode 100644 index 0000000000..9c48ec07a2 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state + +import com.tangem.core.ui.extensions.TextReference + +data class MultiWalletCreateWalletUM( + val onCreateWalletClick: () -> Unit, + val showSeedPhraseOption: Boolean, + val onOtherOptionsClick: () -> Unit, + val dialog: Dialog?, +) { + data class Dialog( + val title: TextReference, + val description: TextReference, + val dismissButtonText: TextReference, + val confirmButtonText: TextReference, + val onConfirm: () -> Unit, + val onDismissButtonClick: () -> Unit, + val onDismiss: () -> Unit, + ) +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt index 1c7fd39182..363655f367 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt @@ -4,6 +4,8 @@ import com.tangem.core.decompose.di.DecomposeComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent import com.tangem.features.onboarding.v2.multiwallet.impl.DefaultOnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.MultiWalletBackupModel +import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model.MultiWalletCreateWalletModel import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel import dagger.Binds import dagger.Module @@ -30,4 +32,14 @@ internal interface ModelModule { @IntoMap @ClassKey(OnboardingMultiWalletModel::class) fun provideModel(model: OnboardingMultiWalletModel): Model + + @Binds + @IntoMap + @ClassKey(MultiWalletBackupModel::class) + fun provideModel2(model: MultiWalletBackupModel): Model + + @Binds + @IntoMap + @ClassKey(MultiWalletCreateWalletModel::class) + fun provideModel3(model: MultiWalletCreateWalletModel): Model } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt index f031b2a3f6..2510f246f6 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt @@ -1,22 +1,14 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.model -import com.tangem.common.CompletionResult -import com.tangem.common.core.TangemSdkError import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.domain.feedback.GetCardInfoUseCase -import com.tangem.domain.feedback.SendFeedbackEmailUseCase -import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.scan.CardDTO import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent -import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM -import com.tangem.sdk.api.TangemSdkManager import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -24,27 +16,14 @@ import javax.inject.Inject internal class OnboardingMultiWalletModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, - private val tangemSdkManager: TangemSdkManager, - private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, - private val getCardInfoUseCase: GetCardInfoUseCase, ) : Model() { private val params = paramsContainer.require() - private val currentScanResponse = MutableStateFlow(params.scanResponse) val state = MutableStateFlow( OnboardingMultiWalletState( currentStep = getInitialStep(), - ), - ) - - val uiState = MutableStateFlow( - OnboardingMultiWalletUM( - onCreateWalletClick = { createWallet(false) }, - showSeedPhraseOption = params.withSeedPhraseFlow, - onOtherOptionsClick = { /* navigate */ }, - onBack = { }, - dialog = null, + currentScanResponse = params.scanResponse, ), ) @@ -53,7 +32,7 @@ internal class OnboardingMultiWalletModel @Inject constructor( } private fun getInitialStep(): OnboardingMultiWalletState.Step { - val card = currentScanResponse.value.card + val card = params.scanResponse.card return when { card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> @@ -78,63 +57,4 @@ internal class OnboardingMultiWalletModel @Inject constructor( } } } - - private fun createWallet(shouldReset: Boolean) { - modelScope.launch { - val result = tangemSdkManager.createProductWallet( - scanResponse = currentScanResponse.value, - shouldReset = shouldReset, - ) - - when (result) { - is CompletionResult.Success -> { - currentScanResponse.update { - it.copy( - card = result.data.card, - derivedKeys = result.data.derivedKeys, - primaryCard = result.data.primaryCard, - ) - } - - // TODO - // Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) - } - - is CompletionResult.Failure -> { - if (result.error is TangemSdkError.WalletAlreadyCreated) { - // show should reset dialog - handleActivationError() - } - } - } - } - } - - private fun handleActivationError() { - uiState.update { - it.copy( - dialog = resetCardDialog( - onConfirm = { - uiState.update { it.copy(dialog = null) } - resetCard() - }, - onDismiss = { - uiState.update { it.copy(dialog = null) } - }, - onDismissButtonClick = ::navigateToSupportScreen, - ), - ) - } - } - - private fun resetCard() { - createWallet(true) - } - - fun navigateToSupportScreen() { - modelScope.launch { - val cardInfo = getCardInfoUseCase(currentScanResponse.value).getOrNull() ?: return@launch - sendFeedbackEmailUseCase(FeedbackEmailType.DirectUserRequest(cardInfo)) - } - } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt index d1b11653dd..b3c6b709c2 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt @@ -1,10 +1,13 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.model +import com.tangem.domain.models.scan.ScanResponse + data class OnboardingMultiWalletState( val currentStep: Step, + val currentScanResponse: ScanResponse, ) { enum class Step { - GeneratePrivateKeys, CreateWallet, AddBackupDevice, FinishBackup, Done + CreateWallet, AddBackupDevice, FinishBackup, Done } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt index fb735811de..73573ab2a6 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt @@ -5,9 +5,10 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onboarding.v2.impl.R fun screenTitleByStep(step: OnboardingMultiWalletState.Step): TextReference = when (step) { - OnboardingMultiWalletState.Step.GeneratePrivateKeys -> TODO() - OnboardingMultiWalletState.Step.CreateWallet -> resourceReference(R.string.onboarding_create_wallet_header) - OnboardingMultiWalletState.Step.AddBackupDevice -> TODO() + OnboardingMultiWalletState.Step.CreateWallet -> + resourceReference(R.string.onboarding_create_wallet_header) + OnboardingMultiWalletState.Step.AddBackupDevice -> + resourceReference(R.string.onboarding_navbar_title_creating_backup) OnboardingMultiWalletState.Step.FinishBackup -> TODO() OnboardingMultiWalletState.Step.Done -> TODO() } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt index 2f4253b9f6..0689f4cb86 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt @@ -17,6 +17,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import timber.log.Timber internal class DefaultOnboardingStepperComponent @AssistedInject constructor( @Assisted val context: AppComponentContext, @@ -27,6 +28,14 @@ internal class DefaultOnboardingStepperComponent @AssistedInject constructor( override val state = instanceKeeper.getOrCreateSimple { MutableStateFlow(params.initState) } + init { + componentScope.launch { + state.collect { + Timber.tag("ASDASD").d("State: $it") + } + } + } + private fun openSupport() { componentScope.launch { val cardInfo = getCardInfoUseCase(params.scanResponse).getOrNull() ?: return@launch @@ -36,10 +45,10 @@ internal class DefaultOnboardingStepperComponent @AssistedInject constructor( @Composable override fun Content(modifier: Modifier) { - val state by state.collectAsStateWithLifecycle() + val uiState by state.collectAsStateWithLifecycle() OnboardingStepper( - state = state, + state = uiState, onBackClick = remember(this) { params.popBack }, onSupportButtonClick = remember(this) { ::openSupport }, modifier = modifier, From 0be9414914ad891d215f03738aaad311cc983e4c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 11:02:04 +0200 Subject: [PATCH 08/33] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 3 - .../tangem/tap/features/main/MainViewModel.kt | 6 +- .../configs/feature_toggles_config.json | 4 - .../data/staking/DefaultStakingRepository.kt | 94 +++++++------------ .../data/staking/di/StakingDataModule.kt | 3 - .../tokens/GetCryptoCurrencyActionsUseCase.kt | 46 ++++----- .../featuretoggles/StakingFeatureToggles.kt | 10 -- .../impl/di/StakingFeatureTogglesModule.kt | 24 ----- .../DefaultStakingFeatureToggles.kt | 17 ---- .../TokenDetailsLoadedBalanceConverter.kt | 4 +- .../state/factory/TokenDetailsStateFactory.kt | 3 - .../viewmodels/TokenDetailsViewModel.kt | 7 +- 12 files changed, 59 insertions(+), 162 deletions(-) delete mode 100644 features/staking/api/src/main/kotlin/com/tangem/features/staking/api/featuretoggles/StakingFeatureToggles.kt delete mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingFeatureTogglesModule.kt delete mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/featuretoggles/DefaultStakingFeatureToggles.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 3b3adab9bf..58c3b2a490 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -7,7 +7,6 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.repository.* import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.feature.swap.domain.api.SwapRepository -import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -221,7 +220,6 @@ internal object TokensDomainModule { quotesRepository: QuotesRepository, networksRepository: NetworksRepository, stakingRepository: StakingRepository, - stakingFeatureToggles: StakingFeatureToggles, dispatchers: CoroutineDispatcherProvider, ): GetCryptoCurrencyActionsUseCase { return GetCryptoCurrencyActionsUseCase( @@ -232,7 +230,6 @@ internal object TokensDomainModule { quotesRepository = quotesRepository, networksRepository = networksRepository, stakingRepository = stakingRepository, - stakingFeatureToggles = stakingFeatureToggles, dispatchers = dispatchers, ) } diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index e9db3ee67e..91726c0820 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -18,7 +18,6 @@ import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase import com.tangem.domain.staking.FetchStakingTokensUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.tap.common.extensions.setContext import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles import com.tangem.tap.features.main.model.MainScreenState @@ -41,7 +40,6 @@ internal class MainViewModel @Inject constructor( private val blockchainSDKFactory: BlockchainSDKFactory, private val userWalletsListManager: UserWalletsListManager, private val dispatchers: CoroutineDispatcherProvider, - stakingFeatureToggles: StakingFeatureToggles, private val fetchStakingTokensUseCase: FetchStakingTokensUseCase, private val apiConfigsManager: ApiConfigsManager, homeFeatureToggles: HomeFeatureToggles, @@ -79,9 +77,7 @@ internal class MainViewModel @Inject constructor( displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() - if (stakingFeatureToggles.isStakingEnabled) { - fetchStakingTokens() - } + fetchStakingTokens() deleteDeprecatedLogsUseCase() } diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 2751047561..2dc8d50df9 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -7,10 +7,6 @@ "name": "WC_SOLANA_TX_SIGN_ENABLED", "version": "5.18.0" }, - { - "name": "STAKING_ENABLED", - "version": "5.15.0" - }, { "name": "IS_ETHEREUM_EIP_1559_ENABLED", "version": "5.17.0" diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 1a40afb7ac..9abafd796c 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -48,7 +48,6 @@ import com.tangem.domain.tokens.model.Network import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase -import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.lib.crypto.BlockchainUtils.isSolana import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.orZero @@ -65,7 +64,6 @@ internal class DefaultStakingRepository( private val stakingBalanceStore: StakingBalanceStore, private val cacheRegistry: CacheRegistry, private val dispatchers: CoroutineDispatcherProvider, - private val stakingFeatureToggle: StakingFeatureToggles, private val walletManagersFacade: WalletManagersFacade, private val getUserWalletUseCase: GetUserWalletUseCase, moshi: Moshi, @@ -320,8 +318,6 @@ internal class DefaultStakingRepository( cryptoCurrency: CryptoCurrency, refresh: Boolean, ) = withContext(dispatchers.io) { - if (!stakingFeatureToggle.isStakingEnabled) return@withContext - cacheRegistry.invokeOnExpire( key = getYieldBalancesKey(userWalletId), skipCache = refresh, @@ -361,30 +357,26 @@ internal class DefaultStakingRepository( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, ): Flow = channelFlow { - if (!stakingFeatureToggle.isStakingEnabled) { - send(YieldBalance.Empty) - } else { - launch(dispatchers.io) { - val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty() - val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)] - ?: error("Could not get integrationId") - stakingBalanceStore.get(userWalletId, address, integrationId) - .distinctUntilChanged() - .collectLatest { - if (it != null) { - send(yieldBalanceConverter.convert(it)) - } else { - error("No yield balance available for currency ${cryptoCurrency.id.value}") - } + launch(dispatchers.io) { + val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty() + val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)] + ?: error("Could not get integrationId") + stakingBalanceStore.get(userWalletId, address, integrationId) + .distinctUntilChanged() + .collectLatest { + if (it != null) { + send(yieldBalanceConverter.convert(it)) + } else { + error("No yield balance available for currency ${cryptoCurrency.id.value}") } - } + } + } - withContext(dispatchers.io) { - fetchSingleYieldBalance( - userWalletId, - cryptoCurrency, - ) - } + withContext(dispatchers.io) { + fetchSingleYieldBalance( + userWalletId, + cryptoCurrency, + ) } }.cancellable() @@ -392,21 +384,17 @@ internal class DefaultStakingRepository( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, ): YieldBalance = withContext(dispatchers.io) { - if (!stakingFeatureToggle.isStakingEnabled) { - YieldBalance.Empty - } else { - fetchSingleYieldBalance(userWalletId, cryptoCurrency) + fetchSingleYieldBalance(userWalletId, cryptoCurrency) - val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty() + val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty() - val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)] - ?: error("Could not get integrationId") + val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)] + ?: error("Could not get integrationId") - val result = stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId) - ?: return@withContext YieldBalance.Error + val result = stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId) + ?: return@withContext YieldBalance.Error - yieldBalanceConverter.convert(result) - } + yieldBalanceConverter.convert(result) } override suspend fun fetchMultiYieldBalance( @@ -414,8 +402,6 @@ internal class DefaultStakingRepository( cryptoCurrencies: List, refresh: Boolean, ) = withContext(dispatchers.io) { - if (!stakingFeatureToggle.isStakingEnabled) return@withContext - cacheRegistry.invokeOnExpire( key = getYieldBalancesKey(userWalletId), skipCache = refresh, @@ -463,19 +449,15 @@ internal class DefaultStakingRepository( userWalletId: UserWalletId, cryptoCurrencies: List, ): Flow = channelFlow { - if (!stakingFeatureToggle.isStakingEnabled) { - send(YieldBalanceList.Empty) - } else { - stakingBalanceStore.get(userWalletId) - .onEach { - val balances = yieldBalanceListConverter.convert(it) - send(balances) - } - .launchIn(scope = this + dispatchers.io) - - withContext(dispatchers.io) { - fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false) + stakingBalanceStore.get(userWalletId) + .onEach { + val balances = yieldBalanceListConverter.convert(it) + send(balances) } + .launchIn(scope = this + dispatchers.io) + + withContext(dispatchers.io) { + fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false) } } @@ -483,13 +465,9 @@ internal class DefaultStakingRepository( userWalletId: UserWalletId, cryptoCurrencies: List, ): YieldBalanceList = withContext(dispatchers.io) { - if (!stakingFeatureToggle.isStakingEnabled) { - YieldBalanceList.Empty - } else { - fetchMultiYieldBalance(userWalletId, cryptoCurrencies) - val result = stakingBalanceStore.getSyncOrNull(userWalletId) ?: return@withContext YieldBalanceList.Error - yieldBalanceListConverter.convert(result) - } + fetchMultiYieldBalance(userWalletId, cryptoCurrencies) + val result = stakingBalanceStore.getSyncOrNull(userWalletId) ?: return@withContext YieldBalanceList.Error + yieldBalanceListConverter.convert(result) } override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean { diff --git a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt index f3a7879d5a..336bcb651e 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt @@ -18,7 +18,6 @@ import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.domain.staking.repositories.* import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.usecase.GetUserWalletUseCase -import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -38,7 +37,6 @@ internal object StakingDataModule { stakingBalanceStore: StakingBalanceStore, cacheRegistry: CacheRegistry, dispatchers: CoroutineDispatcherProvider, - stakingFeatureToggle: StakingFeatureToggles, walletManagersFacade: WalletManagersFacade, getUserWalletUseCase: GetUserWalletUseCase, @NetworkMoshi moshi: Moshi, @@ -49,7 +47,6 @@ internal object StakingDataModule { stakingBalanceStore = stakingBalanceStore, cacheRegistry = cacheRegistry, dispatchers = dispatchers, - stakingFeatureToggle = stakingFeatureToggle, walletManagersFacade = walletManagersFacade, getUserWalletUseCase = getUserWalletUseCase, moshi = moshi, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index ad321616d3..b69ef4f341 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -12,7 +12,6 @@ import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet -import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.isNullOrZero import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -32,7 +31,6 @@ class GetCryptoCurrencyActionsUseCase( private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, private val stakingRepository: StakingRepository, - private val stakingFeatureToggles: StakingFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, ) { @@ -134,28 +132,26 @@ class GetCryptoCurrencyActionsUseCase( } // staking - if (stakingFeatureToggles.isStakingEnabled) { - if (isStakingAvailable(userWallet, cryptoCurrency)) { - val yield = kotlin.runCatching { - stakingRepository.getYield( - cryptoCurrencyId = cryptoCurrency.id, - symbol = cryptoCurrency.symbol, - ) - }.getOrNull() - activeList.add( - TokenActionsState.ActionState.Stake( - unavailabilityReason = ScenarioUnavailabilityReason.None, - yield = yield, - ), + if (isStakingAvailable(userWallet, cryptoCurrency)) { + val yield = kotlin.runCatching { + stakingRepository.getYield( + cryptoCurrencyId = cryptoCurrency.id, + symbol = cryptoCurrency.symbol, ) - } else { - disabledList.add( - TokenActionsState.ActionState.Stake( - unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(cryptoCurrency.name), - yield = null, - ), - ) - } + }.getOrNull() + activeList.add( + TokenActionsState.ActionState.Stake( + unavailabilityReason = ScenarioUnavailabilityReason.None, + yield = yield, + ), + ) + } else { + disabledList.add( + TokenActionsState.ActionState.Stake( + unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(cryptoCurrency.name), + yield = null, + ), + ) } // send @@ -271,9 +267,7 @@ class GetCryptoCurrencyActionsUseCase( } actionsList.add(TokenActionsState.ActionState.Receive(scenario)) } - if (stakingFeatureToggles.isStakingEnabled) { - actionsList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.Unreachable, null)) - } + actionsList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.Unreachable, null)) actionsList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None)) return actionsList diff --git a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/featuretoggles/StakingFeatureToggles.kt b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/featuretoggles/StakingFeatureToggles.kt deleted file mode 100644 index 90ccdc617d..0000000000 --- a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/featuretoggles/StakingFeatureToggles.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.features.staking.api.featuretoggles - -/** - * Staking feature toggles - */ -interface StakingFeatureToggles { - - /** Availability of staking */ - val isStakingEnabled: Boolean -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingFeatureTogglesModule.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingFeatureTogglesModule.kt deleted file mode 100644 index e40072f8bf..0000000000 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/di/StakingFeatureTogglesModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.features.staking.impl.di - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles -import com.tangem.features.staking.impl.featuretoggles.DefaultStakingFeatureToggles -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -/** - * DI module provides implementation of [StakingFeatureToggles] - */ -@Module -@InstallIn(SingletonComponent::class) -internal object StakingFeatureTogglesModule { - - @Provides - @Singleton - fun provideStakingFeatureToggles(featureTogglesManager: FeatureTogglesManager): StakingFeatureToggles { - return DefaultStakingFeatureToggles(featureTogglesManager = featureTogglesManager) - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/featuretoggles/DefaultStakingFeatureToggles.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/featuretoggles/DefaultStakingFeatureToggles.kt deleted file mode 100644 index 735c22b027..0000000000 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/featuretoggles/DefaultStakingFeatureToggles.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.features.staking.impl.featuretoggles - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles - -/** - * Default implementation of staking feature toggles - * - * @property featureTogglesManager manager for getting information about the availability of feature toggles - */ -internal class DefaultStakingFeatureToggles( - private val featureTogglesManager: FeatureTogglesManager, -) : StakingFeatureToggles { - - override val isStakingEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_ENABLED") -} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 676cff23f2..cc7b3c4d38 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -24,7 +24,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsTxHistoryTransactionStateConverter import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getBalance import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents -import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.features.tokendetails.impl.R import com.tangem.lib.crypto.BlockchainUtils.isBSC import com.tangem.lib.crypto.BlockchainUtils.isSolana @@ -46,7 +45,6 @@ internal class TokenDetailsLoadedBalanceConverter( private val symbol: String, private val decimals: Int, private val clickIntents: TokenDetailsClickIntents, - private val stakingFeatureToggles: StakingFeatureToggles, ) : Converter, TokenDetailsState> { private val txHistoryItemConverter by lazy { @@ -100,7 +98,7 @@ internal class TokenDetailsLoadedBalanceConverter( ): TokenDetailsBalanceBlockState { val stakingCryptoAmount = (status.value.yieldBalance as? YieldBalance.Data)?.getTotalWithRewardsStakingBalance() val stakingFiatAmount = stakingCryptoAmount?.let { status.value.fiatRate?.multiply(it) } - val isBalanceSelectorEnabled = stakingFeatureToggles.isStakingEnabled && !stakingCryptoAmount.isNullOrZero() + val isBalanceSelectorEnabled = !stakingCryptoAmount.isNullOrZero() return when (status.value) { is CryptoCurrencyStatus.NoQuote, is CryptoCurrencyStatus.Loaded, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 80f8e8e1dc..626a072c3c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -40,7 +40,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.t import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter.TokenDetailsLoadingTxHistoryModel import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents -import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider import kotlinx.collections.immutable.toImmutableList @@ -59,7 +58,6 @@ internal class TokenDetailsStateFactory( private val networkHasDerivationUseCase: NetworkHasDerivationUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val userWalletId: UserWalletId, - stakingFeatureToggles: StakingFeatureToggles, getStakingIntegrationIdUseCase: GetStakingIntegrationIdUseCase, getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase, symbol: String, @@ -91,7 +89,6 @@ internal class TokenDetailsStateFactory( symbol = symbol, decimals = decimals, clickIntents = clickIntents, - stakingFeatureToggles = stakingFeatureToggles, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 5f5e518a6c..b648664a77 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -72,7 +72,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBala import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig -import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider import com.tangem.utils.coroutines.* @@ -110,7 +109,6 @@ internal class TokenDetailsViewModel @Inject constructor( private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, private val getExtendedPublicKeyForCurrencyUseCase: GetExtendedPublicKeyForCurrencyUseCase, private val getStakingEntryInfoUseCase: GetStakingEntryInfoUseCase, - private val stakingFeatureToggles: StakingFeatureToggles, private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase, private val getYieldUseCase: GetYieldUseCase, private val networkHasDerivationUseCase: NetworkHasDerivationUseCase, @@ -167,7 +165,6 @@ internal class TokenDetailsViewModel @Inject constructor( networkHasDerivationUseCase = networkHasDerivationUseCase, getUserWalletUseCase = getUserWalletUseCase, userWalletId = userWalletId, - stakingFeatureToggles = stakingFeatureToggles, getStakingIntegrationIdUseCase = getStakingIntegrationIdUseCase, getStakingAvailabilityUseCase = getStakingAvailabilityUseCase, symbol = cryptoCurrency.symbol, @@ -240,9 +237,7 @@ internal class TokenDetailsViewModel @Inject constructor( subscribeOnExchangeTransactionsUpdates() updateTxHistory(refresh = false, showItemsLoading = true) - if (stakingFeatureToggles.isStakingEnabled) { - updateStakingInfo() - } + updateStakingInfo() } private fun handleBalanceHiding(owner: LifecycleOwner) { From 0fe5355c74012b313669a66e5ee27ab360e5b642 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 15:32:00 +0500 Subject: [PATCH 09/33] Updated on 2026-08-14 --- .../model/OnboardingManageTokensModel.kt | 6 +- .../impl/DefaultConfirmResidencyComponent.kt | 28 ++++ .../onramp/model/ConfirmResidencyModel.kt | 6 +- .../DefaultSelectCountryComponent.kt | 54 +++++++ .../selectcountry/SelectCountryComponent.kt | 11 ++ .../di/OnrampSelectCountryComponentModule.kt | 20 +++ .../di/OnrampSelectCountryModelModule.kt | 20 +++ .../ConfirmResidencyBottomSheetConfig.kt | 10 ++ .../entity/CountriesListItemUM.kt | 19 +++ .../selectcountry/entity/CountryItemState.kt | 25 ++++ .../selectcountry/entity/CountryListUM.kt | 23 +++ .../entity/CountryListUMController.kt | 54 +++++++ .../UpdateCountryItemsTransformer.kt | 40 ++++++ .../model/MockedCountriesData.kt | 37 +++++ .../model/OnrampSelectCountryModel.kt | 77 ++++++++++ .../selectcountry/ui/CountryListItem.kt | 134 ++++++++++++++++++ .../ui/SelectCountryBottomSheet.kt | 38 +++++ .../tokenlist/entity/TokenListUMController.kt | 23 +++ .../transformer/SearchBarUMTransformer.kt | 33 ----- .../UpdateSearchBarActiveStateTransformer.kt | 19 --- .../tokenlist/model/OnrampTokenListModel.kt | 21 ++- ...heet.kt => ConfirmResidencyBottomSheet.kt} | 0 .../onramp/utils/SearchBarUMTransformer.kt | 14 ++ .../SearchManager.kt} | 4 +- .../UpdateSearchBarActiveStateTransformer.kt | 14 ++ .../UpdateSearchQueryTransformer.kt | 2 +- 26 files changed, 666 insertions(+), 66 deletions(-) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/SelectCountryComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryComponentModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryModelModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/ConfirmResidencyBottomSheetConfig.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountriesListItemUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryItemState.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/{ResidenceBottomSheet.kt => ConfirmResidencyBottomSheet.kt} (100%) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchBarUMTransformer.kt rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{tokenlist/utils/SearchTokensManager.kt => utils/SearchManager.kt} (86%) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/UpdateSearchBarActiveStateTransformer.kt rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{tokenlist/entity/transformer => utils}/UpdateSearchQueryTransformer.kt (81%) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt index 6b4e801849..3a6cc45745 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt @@ -62,9 +62,9 @@ internal class OnboardingManageTokensModel @Inject constructor( .launchIn(modelScope) combine( - manageTokensListManager.currenciesToAdd, - manageTokensListManager.currenciesToRemove, - ::handleChangedCurrencies, + flow = manageTokensListManager.currenciesToAdd, + flow2 = manageTokensListManager.currenciesToRemove, + transform = ::handleChangedCurrencies, ).launchIn(modelScope) observeSearchQueryChanges() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt index 177e697eb8..a55688ca57 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt @@ -4,12 +4,20 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.features.onramp.component.ConfirmResidencyComponent import com.tangem.features.onramp.model.ConfirmResidencyModel +import com.tangem.features.onramp.selectcountry.SelectCountryComponent +import com.tangem.features.onramp.selectcountry.entity.ConfirmResidencyBottomSheetConfig import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheet import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheetContent import dagger.assisted.Assisted @@ -19,9 +27,16 @@ import dagger.assisted.AssistedInject internal class DefaultConfirmResidencyComponent @AssistedInject constructor( @Assisted context: AppComponentContext, @Assisted private val params: ConfirmResidencyComponent.Params, + private val selectCountryComponentFactory: SelectCountryComponent.Factory, ) : ConfirmResidencyComponent, AppComponentContext by context { private val model: ConfirmResidencyModel = getOrCreateModel(params) + private val bottomSheetSlot = childSlot( + source = model.bottomSheetNavigation, + serializer = null, + handleBackButton = false, + childFactory = ::bottomSheetChild, + ) override fun dismiss() { params.onDismiss() @@ -30,6 +45,7 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor( @Composable override fun BottomSheet() { val state by model.state.collectAsStateWithLifecycle() + val bottomSheet by bottomSheetSlot.subscribeAsState() val bottomSheetConfig = remember { TangemBottomSheetConfig( isShow = true, @@ -43,6 +59,18 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor( ConfirmResidencyBottomSheetContent(model = state, modifier = modifier) }, ) + + bottomSheet.child?.instance?.BottomSheet() + } + + private fun bottomSheetChild( + config: ConfirmResidencyBottomSheetConfig, + componentContext: ComponentContext, + ): ComposableBottomSheetComponent = when (config) { + ConfirmResidencyBottomSheetConfig.SelectCountry -> selectCountryComponentFactory.create( + context = childByContext(componentContext), + params = SelectCountryComponent.Params(onDismiss = model.bottomSheetNavigation::dismiss), + ) } @AssistedFactory diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt index 9457285bd3..b4f0283566 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt @@ -1,5 +1,7 @@ package com.tangem.features.onramp.model +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -7,6 +9,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onramp.component.ConfirmResidencyComponent import com.tangem.features.onramp.entity.ConfirmResidencyUM import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.selectcountry.entity.ConfirmResidencyBottomSheetConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject @@ -18,6 +21,7 @@ internal class ConfirmResidencyModel @Inject constructor( ) : Model() { private val params: ConfirmResidencyComponent.Params = paramsContainer.require() + val bottomSheetNavigation: SlotNavigation = SlotNavigation() val state: MutableStateFlow = MutableStateFlow( ConfirmResidencyUM( country = params.countryName, @@ -48,6 +52,6 @@ internal class ConfirmResidencyModel @Inject constructor( } private fun onChangeClick() { - // TODO: [REDACTED_JIRA] + bottomSheetNavigation.activate(ConfirmResidencyBottomSheetConfig.SelectCountry) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt new file mode 100644 index 0000000000..9ac777a7b4 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt @@ -0,0 +1,54 @@ +package com.tangem.features.onramp.selectcountry + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +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.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.features.onramp.selectcountry.model.OnrampSelectCountryModel +import com.tangem.features.onramp.selectcountry.ui.OnrampCountryList +import com.tangem.features.onramp.selectcountry.ui.SelectCountryBottomSheet +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultSelectCountryComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted private val params: SelectCountryComponent.Params, +) : SelectCountryComponent, AppComponentContext by appComponentContext { + + private val model: OnrampSelectCountryModel = getOrCreateModel(params) + + override fun dismiss() { + params.onDismiss() + } + + @Composable + override fun BottomSheet() { + val state by model.state.collectAsStateWithLifecycle() + val bottomSheetConfig = remember { + TangemBottomSheetConfig( + isShow = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } + SelectCountryBottomSheet( + config = bottomSheetConfig, + content = { OnrampCountryList(state = state, modifier = Modifier.fillMaxSize()) }, + ) + } + + @AssistedFactory + interface Factory : SelectCountryComponent.Factory { + override fun create( + context: AppComponentContext, + params: SelectCountryComponent.Params, + ): DefaultSelectCountryComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/SelectCountryComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/SelectCountryComponent.kt new file mode 100644 index 0000000000..8e089ec3d4 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/SelectCountryComponent.kt @@ -0,0 +1,11 @@ +package com.tangem.features.onramp.selectcountry + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent + +interface SelectCountryComponent : ComposableBottomSheetComponent { + + data class Params(val onDismiss: () -> Unit) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryComponentModule.kt new file mode 100644 index 0000000000..86ac4bae7d --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.selectcountry.di + +import com.tangem.features.onramp.selectcountry.DefaultSelectCountryComponent +import com.tangem.features.onramp.selectcountry.SelectCountryComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampSelectCountryComponentModule { + + @Binds + @Singleton + fun bindOnrampSelectCountryComponentFactory( + factory: DefaultSelectCountryComponent.Factory, + ): SelectCountryComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryModelModule.kt new file mode 100644 index 0000000000..1230e7174d --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/di/OnrampSelectCountryModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.selectcountry.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.selectcountry.model.OnrampSelectCountryModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampSelectCountryModelModule { + + @Binds + @IntoMap + @ClassKey(OnrampSelectCountryModel::class) + fun bindOnrampSelectCountryModel(model: OnrampSelectCountryModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/ConfirmResidencyBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/ConfirmResidencyBottomSheetConfig.kt new file mode 100644 index 0000000000..be03ad39a9 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/ConfirmResidencyBottomSheetConfig.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onramp.selectcountry.entity + +import kotlinx.serialization.Serializable + +@Serializable +internal sealed class ConfirmResidencyBottomSheetConfig { + + @Serializable + data object SelectCountry : ConfirmResidencyBottomSheetConfig() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountriesListItemUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountriesListItemUM.kt new file mode 100644 index 0000000000..04b6f8bdee --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountriesListItemUM.kt @@ -0,0 +1,19 @@ +package com.tangem.features.onramp.selectcountry.entity + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.fields.entity.SearchBarUM + +@Immutable +internal sealed interface CountriesListItemUM { + /** Unique ID */ + val id: Any + + data class SearchBar( + override val id: Any = "search_bar", + val searchBarUM: SearchBarUM, + ) : CountriesListItemUM + + data class Country(val state: CountryItemState) : CountriesListItemUM { + override val id = state.id + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryItemState.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryItemState.kt new file mode 100644 index 0000000000..049c7dc55d --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryItemState.kt @@ -0,0 +1,25 @@ +package com.tangem.features.onramp.selectcountry.entity + +import androidx.compose.runtime.Immutable + +@Immutable +internal sealed interface CountryItemState { + /** Unique id */ + val id: String + + data class Loading(override val id: String) : CountryItemState + + data class Unavailable( + override val id: String, + val flagUrl: String, + val countryName: String, + ) : CountryItemState + + data class Content( + override val id: String, + val flagUrl: String, + val countryName: String, + val isSelected: Boolean, + val onClick: () -> Unit, + ) : CountryItemState +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt new file mode 100644 index 0000000000..f1e6365c95 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt @@ -0,0 +1,23 @@ +package com.tangem.features.onramp.selectcountry.entity + +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +internal data class CountryListUM(val items: ImmutableList) { + + /** Get search bar if it exists */ + fun getSearchBar(): CountriesListItemUM.SearchBar? { + return items.firstOrNull() as? CountriesListItemUM.SearchBar + } + + /** Get tokens */ + fun getTokens(): ImmutableList { + if (getSearchBar() == null) return items + + return if (items.size > 1) { + items.subList(fromIndex = 1, toIndex = items.size) + } else { + persistentListOf() + } + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt new file mode 100644 index 0000000000..e91aa92aea --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt @@ -0,0 +1,54 @@ +package com.tangem.features.onramp.selectcountry.entity + +import com.tangem.features.onramp.selectcountry.entity.transformer.UpdateCountryItemsTransformer +import com.tangem.features.onramp.selectcountry.model.MockedCountriesData +import com.tangem.features.onramp.utils.SearchBarUMTransformer +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import timber.log.Timber +import javax.inject.Inject + +internal class CountryListUMController @Inject constructor() { + + val state: StateFlow get() = _state.asStateFlow() + private val _state: MutableStateFlow = MutableStateFlow( + value = CountryListUM( + items = MockedCountriesData.getLoadingItems().map(CountriesListItemUM::Country).toImmutableList(), + ), + ) + + fun update(transformer: UpdateCountryItemsTransformer) { + Timber.d("Applying ${transformer::class.simpleName}") + _state.update(transformer::transform) + } + + fun update(transformer: SearchBarUMTransformer) { + Timber.d("Applying ${transformer::class.simpleName}") + _state.update { prevState -> + val searchBarItem = prevState.getSearchBar() + + if (searchBarItem != null) { + val updatedSearchBar = + searchBarItem.copy(searchBarUM = transformer.transform(searchBarItem.searchBarUM)) + + prevState.copy( + items = persistentListOf( + updatedSearchBar, + *prevState.getTokens().toTypedArray(), + ), + ) + } else { + prevState + } + } + } + + /** Get search bar if it exists */ + fun getSearchBar(): CountriesListItemUM.SearchBar? { + return _state.value.getSearchBar() + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt new file mode 100644 index 0000000000..8af238ebf4 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt @@ -0,0 +1,40 @@ +package com.tangem.features.onramp.selectcountry.entity.transformer + +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.selectcountry.entity.CountriesListItemUM +import com.tangem.features.onramp.selectcountry.entity.CountryItemState +import com.tangem.features.onramp.selectcountry.entity.CountryListUM +import kotlinx.collections.immutable.toImmutableList + +internal class UpdateCountryItemsTransformer( + private val countries: List, + private val onQueryChange: (String) -> Unit, + private val onActiveChange: (Boolean) -> Unit, +) { + + fun transform(prevState: CountryListUM): CountryListUM { + val searchBarItem = prevState.getSearchBar() ?: createSearchBarItem() + + return prevState.copy( + items = (listOfNotNull(searchBarItem) + countries.map(::convertCountry)).toImmutableList(), + ) + } + + // TODO: Temporarily. Will be refactored after implement domain + private fun convertCountry(state: CountryItemState): CountriesListItemUM.Country = + CountriesListItemUM.Country(state) + + private fun createSearchBarItem(): CountriesListItemUM.SearchBar { + return CountriesListItemUM.SearchBar( + searchBarUM = SearchBarUM( + placeholderText = resourceReference(id = R.string.common_search), + query = "", + onQueryChange = onQueryChange, + isActive = false, + onActiveChange = onActiveChange, + ), + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt new file mode 100644 index 0000000000..0c9622c58a --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt @@ -0,0 +1,37 @@ +package com.tangem.features.onramp.selectcountry.model + +import com.tangem.features.onramp.selectcountry.entity.CountryItemState +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +// TODO: Temporarily. Will be deleted after implement domain +@Suppress("MagicNumber") +internal object MockedCountriesData { + + fun getCountryItems(): Flow> = flow { + delay(3_000) // simulate network call + emit(getMockedCountries()) + } + fun getLoadingItems(): List = MutableList(5) { + CountryItemState.Loading("Loading #$it") + } + + private fun getMockedCountries(): List = MutableList(50) { index -> + if (index % 5 == 0) { + CountryItemState.Unavailable( + id = "Country #$index", + flagUrl = "", + countryName = "Country $index", + ) + } else { + CountryItemState.Content( + id = "Country #$index", + flagUrl = "", + countryName = "Country $index", + onClick = {}, + isSelected = index == 7, + ) + } + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt new file mode 100644 index 0000000000..4dd57e776f --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt @@ -0,0 +1,77 @@ +package com.tangem.features.onramp.selectcountry.model + +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.selectcountry.entity.CountryItemState +import com.tangem.features.onramp.selectcountry.entity.CountryListUM +import com.tangem.features.onramp.selectcountry.entity.CountryListUMController +import com.tangem.features.onramp.selectcountry.entity.transformer.UpdateCountryItemsTransformer +import com.tangem.features.onramp.utils.SearchManager +import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer +import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ComponentScoped +internal class OnrampSelectCountryModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val searchManager: SearchManager, + private val countryListUMController: CountryListUMController, +) : Model() { + + val state: StateFlow = countryListUMController.state + + init { + subscribeOnUpdateState() + } + + private fun subscribeOnUpdateState() { + combine( + flow = MockedCountriesData.getCountryItems(), + flow2 = searchManager.query, + ) { countryItems, query -> + val filteredCountryItems = countryItems.filterByQuery(query = query) + + UpdateCountryItemsTransformer( + countries = filteredCountryItems, + onQueryChange = ::onSearchQueryChange, + onActiveChange = ::onSearchBarActiveChange, + ) + } + .onEach(countryListUMController::update) + .flowOn(dispatchers.main) + .launchIn(modelScope) + } + + private fun onSearchQueryChange(newQuery: String) { + val searchBar = countryListUMController.getSearchBar() + if (searchBar?.searchBarUM?.query == newQuery) return + + modelScope.launch { + countryListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery)) + + searchManager.update(newQuery) + } + } + + private fun onSearchBarActiveChange(isActive: Boolean) { + countryListUMController.update( + transformer = UpdateSearchBarActiveStateTransformer( + isActive = isActive, + placeHolder = resourceReference(id = R.string.common_search), + ), + ) + } + + // TODO: Temporarily. Will be refactored after implement domain + private fun List.filterByQuery(query: String): List { + return filter { + (it as? CountryItemState.Content)?.countryName?.contains(query) == true || + (it as? CountryItemState.Unavailable)?.countryName?.contains(query) == true + } + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt new file mode 100644 index 0000000000..1b285b7b66 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt @@ -0,0 +1,134 @@ +package com.tangem.features.onramp.selectcountry.ui + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +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.composed +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import coil.compose.AsyncImage +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.fields.SearchBar +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.selectcountry.entity.CountriesListItemUM +import com.tangem.features.onramp.selectcountry.entity.CountryItemState + +@Composable +internal fun CountryListItem(state: CountriesListItemUM, modifier: Modifier = Modifier) { + when (state) { + is CountriesListItemUM.Country -> CountryItem( + state = state.state, + modifier = modifier.countryClickable(state.state).padding(all = TangemTheme.dimens.spacing16), + ) + is CountriesListItemUM.SearchBar -> SearchBar( + state = state.searchBarUM, + modifier = modifier.padding(all = TangemTheme.dimens.spacing16), + ) + } +} + +@Composable +private fun CountryItem(state: CountryItemState, modifier: Modifier = Modifier) { + when (state) { + is CountryItemState.Content -> ContentCountryItem(state = state, modifier = modifier) + is CountryItemState.Loading -> LoadingCountryItem(modifier) + is CountryItemState.Unavailable -> UnavailableCountryItem(state = state, modifier = modifier) + } +} + +@Composable +private fun ContentCountryItem(state: CountryItemState.Content, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + ) { + AsyncImage( + modifier = Modifier.size(TangemTheme.dimens.size36), + model = state.flagUrl, + contentDescription = null, + ) + Text( + text = state.countryName, + modifier = Modifier.weight(1F), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + ) + if (state.isSelected) { + Icon( + painter = painterResource(id = R.drawable.ic_check_24), + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size24), + tint = TangemTheme.colors.icon.accent, + ) + } + } +} + +@Composable +private fun LoadingCountryItem(modifier: Modifier = Modifier) { + Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12)) { + CircleShimmer(modifier = Modifier.size(TangemTheme.dimens.size36)) + RectangleShimmer( + modifier = Modifier + .align(Alignment.CenterVertically) + .size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12), + radius = TangemTheme.dimens.radius4, + ) + } +} + +@Composable +@Suppress("MagicNumber") +private fun UnavailableCountryItem(state: CountryItemState.Unavailable, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + ) { + AsyncImage( + modifier = Modifier + .size(TangemTheme.dimens.size36) + .alpha(0.4F), + model = state.flagUrl, + contentDescription = null, + ) + Text( + modifier = Modifier.weight(1F), + text = state.countryName, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.disabled, + ) + Text( + text = "Unavailable", + maxLines = 1, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + ) + } +} + +@OptIn(ExperimentalFoundationApi::class) +private fun Modifier.countryClickable(state: CountryItemState): Modifier = composed { + when (state) { + is CountryItemState.Content -> combinedClickable(onClick = state.onClick) + is CountryItemState.Loading, + is CountryItemState.Unavailable, + -> this + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt new file mode 100644 index 0000000000..89c664ce1e --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt @@ -0,0 +1,38 @@ +package com.tangem.features.onramp.selectcountry.ui + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.features.onramp.selectcountry.entity.CountryListUM + +@Composable +internal fun SelectCountryBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) { + TangemBottomSheet( + config = config, + content = { content() }, + ) +} + +@Composable +internal fun OnrampCountryList(state: CountryListUM, modifier: Modifier = Modifier) { + LazyColumn(modifier = modifier) { + items( + items = state.items, + key = { item -> item.id }, + contentType = { item -> item::class.java }, + itemContent = { item -> + CountryListItem( + state = item, + modifier = Modifier + .animateItem() + .fillMaxWidth(), + ) + }, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt index d6263dba6d..07c86963a5 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt @@ -1,6 +1,7 @@ package com.tangem.features.onramp.tokenlist.entity import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.features.onramp.utils.SearchBarUMTransformer import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -35,6 +36,28 @@ internal class TokenListUMController @Inject constructor() { _state.update(transformer::transform) } + fun update(transformer: SearchBarUMTransformer) { + Timber.d("Applying ${transformer::class.simpleName}") + _state.update { prevState -> + val searchBarItem = prevState.getSearchBar() + + if (searchBarItem != null) { + val updatedSearchBar = searchBarItem.copy( + searchBarUM = transformer.transform(searchBarItem.searchBarUM), + ) + + prevState.copy( + availableItems = persistentListOf( + updatedSearchBar, + *prevState.getTokens().toTypedArray(), + ), + ) + } else { + prevState + } + } + } + /** Get search bar if it exists */ fun getSearchBar(): TokensListItemUM.SearchBar? { return _state.value.getSearchBar() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt deleted file mode 100644 index 29a5b2edab..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.features.onramp.tokenlist.entity.transformer - -import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.features.onramp.tokenlist.entity.TokenListUM -import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer -import kotlinx.collections.immutable.persistentListOf - -/** - * Base [SearchBarUM] transformer - * -[REDACTED_AUTHOR] - */ -internal abstract class SearchBarUMTransformer : TokenListUMTransformer { - - abstract fun transform(prevState: SearchBarUM): SearchBarUM - - override fun transform(prevState: TokenListUM): TokenListUM { - val searchBarItem = prevState.getSearchBar() - - return if (searchBarItem != null) { - val updatedSearchBar = searchBarItem.copy(searchBarUM = transform(searchBarItem.searchBarUM)) - - prevState.copy( - availableItems = persistentListOf( - updatedSearchBar, - *prevState.getTokens().toTypedArray(), - ), - ) - } else { - prevState - } - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt deleted file mode 100644 index cf69073114..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.features.onramp.tokenlist.entity.transformer - -import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.features.onramp.impl.R - -internal class UpdateSearchBarActiveStateTransformer(private val isActive: Boolean) : SearchBarUMTransformer() { - - override fun transform(prevState: SearchBarUM): SearchBarUM { - val placeholderText = if (isActive) { - TextReference.EMPTY - } else { - resourceReference(id = R.string.common_search) - } - - return prevState.copy(placeholderText = placeholderText, isActive = isActive) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index ed1b175497..99f147b1c3 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -3,6 +3,7 @@ package com.tangem.features.onramp.tokenlist.model import arrow.core.getOrElse import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -11,15 +12,16 @@ import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList +import com.tangem.features.onramp.impl.R import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.entity.OnrampOperation import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.entity.TokenListUMController -import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateSearchBarActiveStateTransformer -import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateSearchQueryTransformer import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer -import com.tangem.features.onramp.tokenlist.utils.SearchTokensManager +import com.tangem.features.onramp.utils.SearchManager +import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer +import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -30,7 +32,7 @@ internal class OnrampTokenListModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, private val tokenListUMController: TokenListUMController, - private val searchTokensManager: SearchTokensManager, + private val searchManager: SearchManager, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getTokenListUseCase: GetTokenListUseCase, @@ -54,7 +56,7 @@ internal class OnrampTokenListModel @Inject constructor( flow = getTokenListUseCase.launch(userWalletId = params.userWalletId).distinctUntilChanged(), flow2 = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(), flow3 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(), - flow4 = searchTokensManager.query, + flow4 = searchManager.query, ) { maybeTokenList, appCurrency, isBalanceHidden, query -> val currencies = maybeTokenList.getOrElse( ifLoading = { it ?: TokenList.Empty }, @@ -88,12 +90,17 @@ internal class OnrampTokenListModel @Inject constructor( modelScope.launch { tokenListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery)) - searchTokensManager.update(newQuery) + searchManager.update(newQuery) } } private fun onSearchBarActiveChange(isActive: Boolean) { - tokenListUMController.update(transformer = UpdateSearchBarActiveStateTransformer(isActive)) + tokenListUMController.update( + transformer = UpdateSearchBarActiveStateTransformer( + isActive = isActive, + placeHolder = resourceReference(id = R.string.common_search), + ), + ) } private fun List.filterByQuery(query: String): List { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ResidenceBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ConfirmResidencyBottomSheet.kt similarity index 100% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ResidenceBottomSheet.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ConfirmResidencyBottomSheet.kt diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchBarUMTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchBarUMTransformer.kt new file mode 100644 index 0000000000..b943f60862 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchBarUMTransformer.kt @@ -0,0 +1,14 @@ +package com.tangem.features.onramp.utils + +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.utils.transformer.Transformer + +/** + * Base [SearchBarUM] transformer + * +[REDACTED_AUTHOR] + */ +internal abstract class SearchBarUMTransformer : Transformer { + + abstract override fun transform(prevState: SearchBarUM): SearchBarUM +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/utils/SearchTokensManager.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt similarity index 86% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/utils/SearchTokensManager.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt index dde1a1ab4f..b47bbe1810 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/utils/SearchTokensManager.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.tokenlist.utils +package com.tangem.features.onramp.utils import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.withDebounce @@ -12,7 +12,7 @@ import javax.inject.Inject * [REDACTED_AUTHOR] */ -internal class SearchTokensManager @Inject constructor() { +internal class SearchManager @Inject constructor() { val query: Flow get() = _query diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/UpdateSearchBarActiveStateTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/UpdateSearchBarActiveStateTransformer.kt new file mode 100644 index 0000000000..c2727a443d --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/UpdateSearchBarActiveStateTransformer.kt @@ -0,0 +1,14 @@ +package com.tangem.features.onramp.utils + +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.extensions.TextReference + +internal class UpdateSearchBarActiveStateTransformer( + private val isActive: Boolean, + private val placeHolder: TextReference, +) : SearchBarUMTransformer() { + + override fun transform(prevState: SearchBarUM): SearchBarUM { + return prevState.copy(placeholderText = if (isActive) TextReference.EMPTY else placeHolder, isActive = isActive) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/UpdateSearchQueryTransformer.kt similarity index 81% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/UpdateSearchQueryTransformer.kt index 3e463d159a..961fa9cbe1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/UpdateSearchQueryTransformer.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.tokenlist.entity.transformer +package com.tangem.features.onramp.utils import com.tangem.core.ui.components.fields.entity.SearchBarUM From c32507b283a691a0c9f7eb607213d6c2a3efefb7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 20:07:00 +0500 Subject: [PATCH 10/33] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 1 + core/res/src/main/res/values-es/strings.xml | 1 + core/res/src/main/res/values-fr/strings.xml | 1 + core/res/src/main/res/values-ja/strings.xml | 1 + core/res/src/main/res/values-ru/strings.xml | 5 +- core/res/src/main/res/values/strings.xml | 9 ++ .../component/impl/DefaultOnrampComponent.kt | 65 +++++----- .../features/onramp/di/OnrampModelModule.kt | 26 ---- .../onramp/entity/OnrampBottomSheetConfig.kt | 10 -- .../features/onramp/entity/OnrampChild.kt | 12 ++ .../features/onramp/model/OnrampModel.kt | 38 ------ .../selectcountry/SelectCountryComponent.kt | 2 +- .../DefaultOnrampSettingsComponent.kt | 63 ++++++++++ .../settings/OnrampSettingsComponent.kt | 11 ++ .../di/OnrampSettingsComponentModule.kt | 20 ++++ .../settings/di/OnrampSettingsModelModule.kt | 20 ++++ .../settings/entity/OnrampSettingsConfig.kt | 9 ++ .../settings/entity/OnrampSettingsItemUM.kt | 5 + .../settings/entity/OnrampSettingsUM.kt | 8 ++ .../settings/model/OnrampSettingsModel.kt | 34 ++++++ .../settings/ui/OnrampSettingsContent.kt | 111 ++++++++++++++++++ 21 files changed, 344 insertions(+), 108 deletions(-) delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampChild.kt delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/OnrampSettingsComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsComponentModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsModelModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsConfig.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsItemUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/ui/OnrampSettingsContent.kt diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 4313234fb9..198db71cec 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -774,6 +774,7 @@ Belohnungen Stake gesperrt Mehr staken + Gestakeder Betrag Du setzt %1$s ein und ethältst Deine Belohnung %2$s Zum Entsperren antippen Tippe zum Entsperren oder Abstimmen diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index a8fc4365c8..3dc04b55bb 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -856,6 +856,7 @@ desde: %s a: %s validador: %s + El monto mínimo para realizar esta transacción es %1$s. Inténtelo de nuevo Ha escaneado la misma tarjeta. Para crear una billetera gemela, necesite escanear la tarjeta con no. %d Ha escaneado la tarjeta gemela incorrecta. Por favor, intente con otra diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 42eb2abac8..2f534dd458 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -856,6 +856,7 @@ de : %s à : %s validateur : %s + Le montant minimum pour effectuer cette transaction est %1$s. Réessayez Vous avez scanné la même carte. Pour créer un portefeuille jumeau, vous devez scanner la carte portant le numéro %d Vous avez scanné une mauvaise carte jumelle. S\'il vous plaît, essayez-en un autre diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 8cdaeffe4a..d64d288ba7 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -764,6 +764,7 @@ 報酬 ステーキングはロックされています もっとステーキングする + ステーキング中の金額 %1$sをステーキングし、報酬%2$sを受け取ります タップしてロック解除 タップしてロック解除もしくは投票 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index c600ccc00e..50fe9580e5 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -714,6 +714,7 @@ Имя Сумма для стейкинга должна быть не менее %s Согласно правилам сети, сумма стейкинга будет округлена до %1$sTRX. + Сумма для вывода из стейкинга будет округлена до %1$s TRX ввиду особенностей сети. Забрать средства Комиссия за стейкинг аккаунт Стейкинг аккаунт — это специальный счет, на котором хранятся застейканные токены SOL. Он создается при делегировании ваших токенов валидатору для участия в подтверждении транзакций и получении наград. За создание стейкинг аккаунта взимается небольшая комиссия, которая возвращается после завершения стейкинга. @@ -756,6 +757,7 @@ Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждую неделю. Стейкайте безопасно и начинайте получать еженедельные награды. Получите награду за стейкинг + Оставшийся застейканный баланс будет слишком мал для вывода. Вам потребуется застейкать больше средств, чтобы достичь минимальной суммы для вывода. Стейкинг временно недоступен из-за проблем в сети. Пожалуйста, попробуйте позже. Стейкинг в сети %1$s с новым валидатором автоматически переведет ваши текущие застейканные средства на него. Реинвестируйте свои заработанные награды в вашу застейканную сумму, увеличивая потенциальный доход @@ -798,7 +800,7 @@ Отзыв Разблокировать Разблокировка - Сумма для стейкинга должна быть не менее %s + Сумма для вывода из стейкинга должна быть не менее %s Сумма превышает застейканный баланс Вывод из стейкинга Завершение стейкинга @@ -874,6 +876,7 @@ от: %s на: %s валидатор: %s + Минимальная сумма транзакции равна %1$s. Попробовать снова Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 3a507e68ab..1e4d43cba5 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -551,10 +551,16 @@ Access code restore Identical cards Access code + Search by country + Unavailable + Search by currency Pay with Our services are not available in this country Change or confirm it Your residence has been identified as + Residence + Please select the correct country to ensure accurate payment options and services. + Settings Via Group By balance @@ -741,6 +747,8 @@ Staking allows you to earn %1$s. Your staking rewards arrive every week. Stake securely and start earning weekly rewards Earn staking rewards + Your remaining staked balance will be too low to unstake. You’ll need to stake more to meet the minimum unstake amount. + Low staked balance Staking is currently unavailable due to network conditions. Please try again later. Staking in the %1$s network with a new validator will automatically transfer all previously staked funds to this validator Reinvests your earned rewards in your staked amount, increasing potential earnings. @@ -861,6 +869,7 @@ from: %s to: %s validator: %s + The minimum transaction amount is %1$s. Try again You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d You\'ve scanned wrong twin card. Please try another one diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt index 7ec0fb692a..1957c1def4 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt @@ -1,61 +1,62 @@ package com.tangem.features.onramp.component.impl -import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children +import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState -import com.arkivanov.decompose.router.slot.childSlot -import com.arkivanov.decompose.router.slot.dismiss +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.childStack import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext -import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.onramp.component.OnrampComponent -import com.tangem.features.onramp.component.ConfirmResidencyComponent -import com.tangem.features.onramp.entity.OnrampBottomSheetConfig -import com.tangem.features.onramp.model.OnrampModel +import com.tangem.features.onramp.entity.OnrampChild +import com.tangem.features.onramp.settings.OnrampSettingsComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +@Suppress("UnusedPrivateMember") internal class DefaultOnrampComponent @AssistedInject constructor( @Assisted context: AppComponentContext, - @Assisted private val params: OnrampComponent.Params, - private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory, + @Assisted params: OnrampComponent.Params, + private val settingsComponentFactory: OnrampSettingsComponent.Factory, ) : OnrampComponent, AppComponentContext by context { - private val model: OnrampModel = getOrCreateModel(params) - private val confirmResidencyBottomSheetSlot = childSlot( - source = model.bottomSheetNavigation, - serializer = null, + private val navigation = StackNavigation() + private val contentStack = childStack( + key = "onramp_content_stack", + source = navigation, + serializer = OnrampChild.serializer(), + initialConfiguration = OnrampChild.Settings, handleBackButton = false, - childFactory = ::bottomSheetChild, + childFactory = ::screenChild, ) @Composable override fun Content(modifier: Modifier) { - val bottomSheet by confirmResidencyBottomSheetSlot.subscribeAsState() - BackHandler(onBack = router::pop) + val childStack by contentStack.subscribeAsState() - bottomSheet.child?.instance?.BottomSheet() + Children( + stack = childStack, + animation = stackAnimation(), + ) { child -> + child.instance.Content(modifier = modifier) + } } - private fun bottomSheetChild( - config: OnrampBottomSheetConfig, - componentContext: ComponentContext, - ): ComposableBottomSheetComponent = when (config) { - OnrampBottomSheetConfig.ConfirmResidency -> confirmResidencyComponentFactory.create( - context = childByContext(componentContext), - params = ConfirmResidencyComponent.Params( - countryName = "United States", - isOnrampSupported = true, - countryFlagUrl = "https://hatscripts.github.io/circle-flags/flags/us.svg", - onDismiss = model.bottomSheetNavigation::dismiss, - ), - ) - } + private fun screenChild(config: OnrampChild, componentContext: ComponentContext): ComposableContentComponent = + when (config) { + OnrampChild.Settings -> settingsComponentFactory.create( + context = childByContext(componentContext), + // params = OnrampSettingsComponent.Params(navigation::pop) // todo uncomment after [REDACTED_JIRA] + params = OnrampSettingsComponent.Params(router::pop), + ) + OnrampChild.Main -> TODO("[REDACTED_JIRA]") + } @AssistedFactory interface Factory : OnrampComponent.Factory { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt deleted file mode 100644 index 41f90f6d5a..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.features.onramp.di - -import com.tangem.core.decompose.di.DecomposeComponent -import com.tangem.core.decompose.model.Model -import com.tangem.features.onramp.model.OnrampModel -import com.tangem.features.onramp.model.ConfirmResidencyModel -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap - -@Module -@InstallIn(DecomposeComponent::class) -internal interface OnrampModelModule { - - @Binds - @IntoMap - @ClassKey(OnrampModel::class) - fun provideOnrampModel(model: OnrampModel): Model - - @Binds - @IntoMap - @ClassKey(ConfirmResidencyModel::class) - fun provideConfirmResidencyModel(model: ConfirmResidencyModel): Model -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt deleted file mode 100644 index 81289b4cdf..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.features.onramp.entity - -import kotlinx.serialization.Serializable - -@Serializable -internal sealed class OnrampBottomSheetConfig { - - @Serializable - data object ConfirmResidency : OnrampBottomSheetConfig() -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampChild.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampChild.kt new file mode 100644 index 0000000000..e18f168bff --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampChild.kt @@ -0,0 +1,12 @@ +package com.tangem.features.onramp.entity + +import kotlinx.serialization.Serializable + +@Serializable +internal sealed class OnrampChild { + @Serializable + data object Main : OnrampChild() + + @Serializable + data object Settings : OnrampChild() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt deleted file mode 100644 index d1f35e1fca..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.tangem.features.onramp.model - -import com.arkivanov.decompose.router.slot.SlotNavigation -import com.arkivanov.decompose.router.slot.activate -import com.tangem.core.decompose.di.ComponentScoped -import com.tangem.core.decompose.model.Model -import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.core.decompose.navigation.Router -import com.tangem.features.onramp.component.OnrampComponent -import com.tangem.features.onramp.entity.OnrampBottomSheetConfig -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import javax.inject.Inject - -@Suppress("MagicNumber") -@ComponentScoped -internal class OnrampModel @Inject constructor( - override val dispatchers: CoroutineDispatcherProvider, - private val router: Router, - paramsContainer: ParamsContainer, -) : Model() { - - @Suppress("UnusedPrivateMember") - private val params: OnrampComponent.Params = paramsContainer.require() - val bottomSheetNavigation: SlotNavigation = SlotNavigation() - - init { - modelScope.launch { - delay(1500) - bottomSheetNavigation.activate(OnrampBottomSheetConfig.ConfirmResidency) - } - } - - fun pop() { - router.pop() - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/SelectCountryComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/SelectCountryComponent.kt index 8e089ec3d4..96a98be090 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/SelectCountryComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/SelectCountryComponent.kt @@ -3,7 +3,7 @@ package com.tangem.features.onramp.selectcountry import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableBottomSheetComponent -interface SelectCountryComponent : ComposableBottomSheetComponent { +internal interface SelectCountryComponent : ComposableBottomSheetComponent { data class Params(val onDismiss: () -> Unit) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt new file mode 100644 index 0000000000..2fff47ec00 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt @@ -0,0 +1,63 @@ +package com.tangem.features.onramp.settings + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.features.onramp.selectcountry.SelectCountryComponent +import com.tangem.features.onramp.settings.entity.OnrampSettingsConfig +import com.tangem.features.onramp.settings.model.OnrampSettingsModel +import com.tangem.features.onramp.settings.ui.OnrampSettingsContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnrampSettingsComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted private val params: OnrampSettingsComponent.Params, + private val selectCountryComponentFactory: SelectCountryComponent.Factory, +) : OnrampSettingsComponent, AppComponentContext by appComponentContext { + + private val model: OnrampSettingsModel = getOrCreateModel(params) + private val bottomSheetSlot = childSlot( + source = model.bottomSheetNavigation, + serializer = null, + handleBackButton = false, + childFactory = ::bottomSheetChild, + ) + + @Composable + override fun Content(modifier: Modifier) { + BackHandler(onBack = params.onBack) + OnrampSettingsContent(modifier = modifier, state = model.state) + + val bottomSheet by bottomSheetSlot.subscribeAsState() + bottomSheet.child?.instance?.BottomSheet() + } + + private fun bottomSheetChild( + config: OnrampSettingsConfig, + componentContext: ComponentContext, + ): ComposableBottomSheetComponent = when (config) { + OnrampSettingsConfig.SelectCountry -> selectCountryComponentFactory.create( + context = childByContext(componentContext), + params = SelectCountryComponent.Params(model.bottomSheetNavigation::dismiss), + ) + } + + @AssistedFactory + interface Factory : OnrampSettingsComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnrampSettingsComponent.Params, + ): DefaultOnrampSettingsComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/OnrampSettingsComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/OnrampSettingsComponent.kt new file mode 100644 index 0000000000..2514bbb00b --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/OnrampSettingsComponent.kt @@ -0,0 +1,11 @@ +package com.tangem.features.onramp.settings + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +internal interface OnrampSettingsComponent : ComposableContentComponent { + + data class Params(val onBack: () -> Unit) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsComponentModule.kt new file mode 100644 index 0000000000..648bf03301 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.settings.di + +import com.tangem.features.onramp.settings.DefaultOnrampSettingsComponent +import com.tangem.features.onramp.settings.OnrampSettingsComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampSettingsComponentModule { + + @Binds + @Singleton + fun bindOnrampSettingsComponentFactory( + factory: DefaultOnrampSettingsComponent.Factory, + ): OnrampSettingsComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsModelModule.kt new file mode 100644 index 0000000000..f62678e7ee --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/di/OnrampSettingsModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.settings.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.settings.model.OnrampSettingsModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampSettingsModelModule { + + @Binds + @IntoMap + @ClassKey(OnrampSettingsModel::class) + fun bindOnrampSettingsModelModel(model: OnrampSettingsModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsConfig.kt new file mode 100644 index 0000000000..11ba9a2f0b --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsConfig.kt @@ -0,0 +1,9 @@ +package com.tangem.features.onramp.settings.entity + +import kotlinx.serialization.Serializable + +@Serializable +sealed class OnrampSettingsConfig { + + data object SelectCountry : OnrampSettingsConfig() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsItemUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsItemUM.kt new file mode 100644 index 0000000000..f957739a26 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsItemUM.kt @@ -0,0 +1,5 @@ +package com.tangem.features.onramp.settings.entity + +sealed class OnrampSettingsItemUM { + data class Residence(val countryName: String, val flagUrl: String, val onClick: () -> Unit) : OnrampSettingsItemUM() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsUM.kt new file mode 100644 index 0000000000..0997eb67d1 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsUM.kt @@ -0,0 +1,8 @@ +package com.tangem.features.onramp.settings.entity + +import kotlinx.collections.immutable.ImmutableList + +internal data class OnrampSettingsUM( + val onBack: () -> Unit, + val items: ImmutableList, +) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt new file mode 100644 index 0000000000..edff4909ee --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt @@ -0,0 +1,34 @@ +package com.tangem.features.onramp.settings.model + +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.onramp.settings.OnrampSettingsComponent +import com.tangem.features.onramp.settings.entity.OnrampSettingsConfig +import com.tangem.features.onramp.settings.entity.OnrampSettingsItemUM +import com.tangem.features.onramp.settings.entity.OnrampSettingsUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.toImmutableList +import javax.inject.Inject + +@ComponentScoped +internal class OnrampSettingsModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, +) : Model() { + + private val params: OnrampSettingsComponent.Params = paramsContainer.require() + val bottomSheetNavigation: SlotNavigation = SlotNavigation() + val state = OnrampSettingsUM( + onBack = params.onBack, + items = listOf( + OnrampSettingsItemUM.Residence( + countryName = "United States of America", + flagUrl = "", + onClick = { bottomSheetNavigation.activate(OnrampSettingsConfig.SelectCountry) }, + ), + ).toImmutableList(), + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/ui/OnrampSettingsContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/ui/OnrampSettingsContent.kt new file mode 100644 index 0000000000..949a3af30e --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/ui/OnrampSettingsContent.kt @@ -0,0 +1,111 @@ +package com.tangem.features.onramp.settings.ui + +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.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.key +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.util.fastForEach +import coil.compose.AsyncImage +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.WindowInsetsZero +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.settings.entity.OnrampSettingsItemUM +import com.tangem.features.onramp.settings.entity.OnrampSettingsUM +import kotlinx.collections.immutable.ImmutableList + +@Composable +internal fun OnrampSettingsContent(state: OnrampSettingsUM, modifier: Modifier = Modifier) { + Scaffold( + modifier = modifier, + contentWindowInsets = WindowInsetsZero, + containerColor = TangemTheme.colors.background.secondary, + topBar = { + AppBarWithBackButton( + modifier = Modifier.statusBarsPadding(), + onBackClick = state.onBack, + text = stringResource(id = R.string.onramp_settings_title), + iconRes = R.drawable.ic_close_24, + ) + }, + content = { innerPadding -> + Content( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize() + .padding(horizontal = TangemTheme.dimens.spacing16), + items = state.items, + ) + }, + ) +} + +@Composable +private fun Content(items: ImmutableList, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + items.fastForEach { model -> + key(model) { + when (model) { + is OnrampSettingsItemUM.Residence -> ResidenceSection(model) + } + } + } + } +} + +@Composable +private fun ResidenceSection(state: OnrampSettingsItemUM.Residence, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + Row( + modifier = Modifier + .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) + .background(TangemTheme.colors.background.primary) + .clickable(onClick = state.onClick) + .padding(TangemTheme.dimens.spacing12) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(id = R.string.onramp_settings_residence), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.subtitle2, + ) + SpacerWMax() + AsyncImage( + modifier = Modifier.size(TangemTheme.dimens.size20), + model = state.flagUrl, + contentDescription = null, + ) + Text( + text = state.countryName, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body2, + ) + Icon( + painter = painterResource(id = R.drawable.ic_chevron_right_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) + } + Text( + text = stringResource(id = R.string.onramp_settings_residence_description), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption2, + ) + } +} \ No newline at end of file From bed70c4dc843bd3cb0202fde995982b9282ba996 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 16:29:41 +0400 Subject: [PATCH 11/33] Updated on 2026-08-14 --- .../common/redux/global/GlobalMiddleware.kt | 8 +++- .../java/com/tangem/tap/di/ActivityModule.kt | 3 ++ .../CurrencyExchangeManager.kt | 14 ++++++ .../exchangeServices/DefaultRampManager.kt | 13 ++++++ .../exchangeServices/ExchangeService.kt | 13 ++++++ .../mercuryo/MercuryoService.kt | 46 ++++++++++++++----- .../moonpay/MoonPayService.kt | 30 +++++++++++- .../com/tangem/tap/proxy/AppStateHolder.kt | 7 +-- domain/legacy/build.gradle.kts | 1 + .../domain/exchange/RampStateManager.kt | 6 +++ 10 files changed, 120 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index c4366e8215..b8865305c5 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -64,12 +64,16 @@ private fun handleAction(action: Action, appState: () -> AppState?) { } val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card } + val buyService = makeBuyExchangeService(config) + val sellService = makeSellExchangeService(config) val exchangeManager = CurrencyExchangeManager( - buyService = makeBuyExchangeService(config), - sellService = makeSellExchangeService(config), + buyService = buyService, + sellService = sellService, primaryRules = CardExchangeRules(cardProvider), ) // TODO: for refactoring (after remove old design refactor CurrencyExchangeManager and use 1 instance) + store.inject(DaggerGraphState::appStateHolder).buyService = buyService + store.inject(DaggerGraphState::appStateHolder).sellService = sellService store.inject(DaggerGraphState::appStateHolder).exchangeService = exchangeManager store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager)) store.dispatchOnMain(GlobalAction.ExchangeManager.Update) diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index 3908c30790..9e18be5306 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -11,6 +11,7 @@ import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.network.exchangeServices.DefaultRampManager import com.tangem.tap.proxy.AppStateHolder +import com.tangem.utils.Provider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -46,6 +47,8 @@ internal object ActivityModule { ): RampStateManager { return DefaultRampManager( exchangeService = appStateHolder.exchangeService, + buyService = Provider { requireNotNull(appStateHolder.buyService) }, + sellService = Provider { requireNotNull(appStateHolder.sellService) }, marketsCryptoCurrencyRepository = marketCryptoCurrencyRepository, ) } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt index 71c317e080..0e9092a1fa 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt @@ -8,6 +8,8 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.domain.common.TapWorkarounds.isTangemTwins +import com.tangem.domain.core.utils.lceContent +import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency @@ -19,6 +21,8 @@ import com.tangem.tap.domain.model.Currency import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import java.math.BigDecimal /** @@ -30,9 +34,19 @@ class CurrencyExchangeManager( private val primaryRules: ExchangeRules, ) : ExchangeService { + override val initializationStatus: StateFlow + get() = _initializationStatus + + private val _initializationStatus: MutableStateFlow = + MutableStateFlow(value = lceLoading()) + override suspend fun update() { + _initializationStatus.value = lceLoading() + buyService.update() sellService.update() + + _initializationStatus.value = lceContent() } override fun isBuyAllowed(): Boolean = primaryRules.isBuyAllowed() && buyService.isBuyAllowed() diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 5d6d7c5b2b..2ce18c2fe0 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -5,13 +5,18 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.Provider +import kotlinx.coroutines.flow.Flow class DefaultRampManager( private val exchangeService: ExchangeService?, + private val buyService: Provider, + private val sellService: Provider, private val marketsCryptoCurrencyRepository: MarketCryptoCurrencyRepository, ) : RampStateManager { private val cryptoCurrencyConverter = CryptoCurrencyConverter() + override fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean { return exchangeService?.availableForBuy( scanResponse, @@ -28,4 +33,12 @@ class DefaultRampManager( override suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { return marketsCryptoCurrencyRepository.isExchangeable(userWalletId, cryptoCurrency) } + + override fun getBuyInitializationStatus(): Flow { + return buyService.invoke().initializationStatus + } + + override fun getSellInitializationStatus(): Flow { + return sellService.invoke().initializationStatus + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt index 495bcec410..e49b386c9d 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt @@ -1,8 +1,14 @@ package com.tangem.tap.network.exchangeServices +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.domain.model.Currency +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +typealias ExchangeServiceInitializationStatus = Lce interface Exchanger { fun isBuyAllowed(): Boolean @@ -12,10 +18,17 @@ interface Exchanger { } interface ExchangeService : Exchanger, ExchangeUrlBuilder { + + val initializationStatus: StateFlow + suspend fun update() companion object { fun dummy(): ExchangeService = object : ExchangeService { + + override val initializationStatus: StateFlow = + MutableStateFlow(value = lceLoading()) + override suspend fun update() {} override fun isBuyAllowed(): Boolean = false override fun isSellAllowed(): Boolean = false diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt index 50b558907b..bd46579044 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -6,12 +6,19 @@ import com.tangem.common.extensions.calculateSha512 import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.common.services.performRequest +import com.tangem.domain.core.utils.lceContent +import com.tangem.domain.core.utils.lceError +import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.domain.model.Currency import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.network.exchangeServices.ExchangeService +import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import timber.log.Timber import java.util.concurrent.CopyOnWriteArrayList /** @@ -19,10 +26,34 @@ import java.util.concurrent.CopyOnWriteArrayList */ internal class MercuryoService(private val environment: MercuryoEnvironment) : ExchangeService { + override val initializationStatus: StateFlow + get() = _initializationStatus + + private val _initializationStatus: MutableStateFlow = + MutableStateFlow(value = lceLoading()) + private val api: MercuryoApi = environment.mercuryoApi private val availableMercuryoCurrencies = CopyOnWriteArrayList() + override suspend fun update() { + Timber.i("Start updating") + _initializationStatus.value = lceLoading() + + val result = performRequest { api.currencies(environment.apiVersion) } + when { + result is Result.Success && result.data.status == RESPONSE_SUCCESS_STATUS_CODE -> { + handleSuccessfullyUpdatedData(data = result.data.data) + } + result is Result.Failure -> { + availableMercuryoCurrencies.clear() + + Timber.e("Failed to load currencies", result.error) + _initializationStatus.value = result.error.lceError() + } + } + } + override fun isBuyAllowed(): Boolean = true override fun isSellAllowed(): Boolean = false @@ -42,18 +73,6 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E override fun availableForSell(currency: Currency): Boolean = false - override suspend fun update() { - val result = performRequest { api.currencies(environment.apiVersion) } - when { - result is Result.Success && result.data.status == RESPONSE_SUCCESS_STATUS_CODE -> { - handleSuccessfullyUpdatedData(data = result.data.data) - } - result is Result.Failure -> { - availableMercuryoCurrencies.clear() - } - } - } - override fun getUrl( action: CurrencyExchangeManager.Action, cryptoCurrency: CryptoCurrency, @@ -89,6 +108,9 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E private fun handleSuccessfullyUpdatedData(data: MercuryoCurrenciesResponse.Data) { availableMercuryoCurrencies.clear() availableMercuryoCurrencies.addAll(data.config.cryptoCurrencies) + + Timber.i("Successfully updated") + _initializationStatus.value = lceContent() } private fun signature(address: String) = (address + environment.secret).calculateSha512().toHexString().lowercase() diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index 26b26f0b61..90011a2d77 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -7,13 +7,20 @@ import com.tangem.common.services.Result import com.tangem.common.services.performRequest import com.tangem.datasource.api.common.createRetrofitInstance import com.tangem.domain.common.extensions.withIOContext +import com.tangem.domain.core.utils.lceContent +import com.tangem.domain.core.utils.lceError +import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.tap.domain.model.Currency import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.network.exchangeServices.ExchangeService +import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPayAvailableCurrency +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import timber.log.Timber import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec @@ -23,6 +30,12 @@ class MoonPayService( private val logEnabled: Boolean, ) : ExchangeService { + override val initializationStatus: StateFlow + get() = _initializationStatus + + private val _initializationStatus: MutableStateFlow = + MutableStateFlow(value = lceLoading()) + private val api: MoonPayApi by lazy { createRetrofitInstance( baseUrl = MoonPayApi.MOOONPAY_BASE_URL, @@ -34,14 +47,25 @@ class MoonPayService( override suspend fun update() { withIOContext { + Timber.i("Start updating") + _initializationStatus.value = lceLoading() + performRequest { val userStatus = when (val result = performRequest { api.getUserStatus(apiKey) }) { - is Result.Failure -> return@performRequest + is Result.Failure -> { + Timber.e("Failed to load user status", result.error) + _initializationStatus.value = result.error.lceError() + return@performRequest + } is Result.Success -> result.data } val currencies = when (val result = performRequest { api.getCurrencies(apiKey) }) { - is Result.Failure -> return@performRequest + is Result.Failure -> { + Timber.e("Failed to load currencies", result.error) + _initializationStatus.value = result.error.lceError() + return@performRequest + } is Result.Success -> result.data } @@ -58,6 +82,8 @@ class MoonPayService( ) } + Timber.i("Successfully updated") + _initializationStatus.value = lceContent() status = MoonPayStatus(currenciesToSell, userStatus, currencies) } } diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index f626aa4238..18c616034a 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -1,6 +1,5 @@ package com.tangem.tap.proxy -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.redux.StateDialog @@ -26,10 +25,8 @@ class AppStateHolder @Inject constructor() : ReduxStateHolder { var mainStore: Store? = null var tangemSdkManager: TangemSdkManager? = null var exchangeService: ExchangeService? = null - - fun getActualCard(): CardDTO? { - return scanResponse?.card - } + var buyService: ExchangeService? = null + var sellService: ExchangeService? = null override fun dispatch(action: Action) { mainStore?.dispatch(action) diff --git a/domain/legacy/build.gradle.kts b/domain/legacy/build.gradle.kts index f01a2e6a96..ab9922a279 100644 --- a/domain/legacy/build.gradle.kts +++ b/domain/legacy/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation(projects.common) implementation(projects.libs.auth) implementation(projects.libs.blockchainSdk) + implementation(projects.domain.core) implementation(projects.domain.demo) implementation(projects.domain.models) implementation(projects.domain.tokens.models) diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index b7a3bde7fc..423ecdc390 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -1,8 +1,10 @@ package com.tangem.domain.exchange +import com.tangem.domain.core.lce.Lce import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow /** * Manager that holds info about available actions as Sell and Buy @@ -14,4 +16,8 @@ interface RampStateManager { fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean + + fun getBuyInitializationStatus(): Flow> + + fun getSellInitializationStatus(): Flow> } \ No newline at end of file From 896644826164476f6855d5f9679db1d0b6f37bac Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Nov 2024 17:20:12 +0500 Subject: [PATCH 12/33] Updated on 2026-08-14 --- app/build.gradle.kts | 2 + .../tap/di/domain/OnrampDomainModule.kt | 27 +++ .../datasource/api/onramp/MockedOnrampApi.kt | 178 ------------------ .../com/tangem/datasource/di/NetworkModule.kt | 33 ++-- .../local/preferences/PreferencesKeys.kt | 2 + .../data/onramp/DefaultOnrampRepository.kt | 28 +++ .../onramp/converters/CurrencyConverter.kt | 11 +- .../tangem/data/onramp/di/OnrampDataModule.kt | 8 +- .../domain/onramp/model/OnrampCurrencies.kt | 3 + .../onramp/GetOnrampCurrenciesUseCase.kt | 23 +++ .../OnrampSaveDefaultCurrencyUseCase.kt | 11 ++ .../onramp/repositories/OnrampRepository.kt | 4 + features/onramp/impl/build.gradle.kts | 1 + .../impl/DefaultConfirmResidencyComponent.kt | 2 +- .../PreviewConfirmResidencyComponent.kt | 13 +- .../DefaultSelectCountryComponent.kt | 2 +- .../entity/CountryListUMController.kt | 3 - .../UpdateCountryItemsTransformer.kt | 5 +- .../DefaultSelectCurrencyComponent.kt | 56 ++++++ .../selectcurrency/SelectCurrencyComponent.kt | 11 ++ .../di/OnrampSelectCurrencyComponentModule.kt | 20 ++ .../di/OnrampSelectCurrencyModelModule.kt | 20 ++ .../selectcurrency/entity/CurrenciesListUM.kt | 31 +++ .../entity/CurrenciesSection.kt | 6 + .../entity/CurrencyItemState.kt | 19 ++ .../entity/CurrencyListController.kt | 41 ++++ .../UpdateCurrencyItemsTransformer.kt | 65 +++++++ .../model/OnrampSelectCurrencyModel.kt | 107 +++++++++++ .../ui/SelectCurrencyBottomSheet.kt | 145 ++++++++++++++ .../features/onramp/utils/SearchManager.kt | 1 + 30 files changed, 667 insertions(+), 211 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt create mode 100644 domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrencies.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/SelectCurrencyComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyComponentModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesListUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesSection.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyItemState.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9029856167..f08ee1b88d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -74,6 +74,7 @@ dependencies { implementation(projects.domain.walletConnect) implementation(projects.domain.markets) implementation(projects.domain.manageTokens) + implementation(projects.domain.onramp) implementation(projects.common) implementation(projects.common.routing) @@ -112,6 +113,7 @@ dependencies { implementation(projects.data.walletConnect) implementation(projects.data.markets) implementation(projects.data.manageTokens) + implementation(projects.data.onramp) /** Features */ implementation(projects.features.onboarding) diff --git a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt new file mode 100644 index 0000000000..b485886c06 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.di.domain + +import com.tangem.domain.onramp.GetOnrampCurrenciesUseCase +import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase +import com.tangem.domain.onramp.repositories.OnrampRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object OnrampDomainModule { + + @Provides + @Singleton + fun provideGetOnrampCurrenciesUseCase(onrampRepository: OnrampRepository): GetOnrampCurrenciesUseCase { + return GetOnrampCurrenciesUseCase(onrampRepository) + } + + @Provides + @Singleton + fun provideOnrampSaveDefaultCurrencyUseCase(onrampRepository: OnrampRepository): OnrampSaveDefaultCurrencyUseCase { + return OnrampSaveDefaultCurrencyUseCase(onrampRepository) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt deleted file mode 100644 index a960766de8..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt +++ /dev/null @@ -1,178 +0,0 @@ -package com.tangem.datasource.api.onramp - -import com.tangem.datasource.api.common.response.ApiResponse -import com.tangem.datasource.api.onramp.models.common.OnrampDestinationDTO -import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest -import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse -import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse -import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse -import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO -import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO -import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO -import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO - -internal class MockedOnrampApi : OnrampApi { - override suspend fun getCurrencies(): ApiResponse> = ApiResponse.Success( - COUNTRIES.map(OnrampCountryDTO::defaultCurrency), - ) - - override suspend fun getCountries(): ApiResponse> = ApiResponse.Success(COUNTRIES + RUSSIA) - - override suspend fun getCountryByIp(): ApiResponse = ApiResponse.Success(RUSSIA) - - override suspend fun getPaymentMethods(): ApiResponse> = ApiResponse.Success( - listOf( - PaymentMethodDTO(id = "google", name = "Google Play", image = ""), - PaymentMethodDTO(id = "apple", name = "Apple Pay", image = ""), - PaymentMethodDTO(id = "card", name = "Card", image = ""), - ), - ) - - override suspend fun getPairs(body: OnrampPairsRequest): ApiResponse> = ApiResponse.Success( - listOf( - OnrampPairDTO( - fromCurrencyCode = "USD", - to = OnrampDestinationDTO(contractAddress = "0xcontract_address", network = "ethereum"), - providers = listOf(), - ), - ), - ) - - override suspend fun getQuote( - fromCurrencyCode: String, - toContractAddress: String, - toNetwork: String, - paymentMethod: String, - countryCode: String, - fromAmount: String, - toDecimals: Int, - providerId: String, - ): ApiResponse { - TODO("Not yet implemented") - } - - override suspend fun getData( - fromCurrencyCode: String, - toContractAddress: String, - toNetwork: String, - paymentMethod: String, - countryCode: String, - fromAmount: String, - toDecimals: Int, - providerId: String, - toAddress: String, - redirectUrl: String, - language: String?, - theme: String?, - requestId: String, - ): ApiResponse { - TODO("Not yet implemented") - } - - override suspend fun getStatus(txId: String): ApiResponse { - TODO("Not yet implemented") - } - - private companion object { - private val RUSSIA = OnrampCountryDTO( - name = "Russia", - code = "RU", - image = "https://hatscripts.github.io/circle-flags/flags/ru.svg", - alpha3 = "RUS", - continent = "", - defaultCurrency = OnrampCurrencyDTO( - name = "Russian ruble", - code = "RUB", - image = "https://hatscripts.github.io/circle-flags/flags/ru.svg", - precision = 2, - ), - onrampAvailable = false, - ) - private val COUNTRIES = listOf( - OnrampCountryDTO( - name = "United States of America", - code = "USA", - image = "https://hatscripts.github.io/circle-flags/flags/us.svg", - alpha3 = "USA", - continent = "", - defaultCurrency = OnrampCurrencyDTO( - name = "US Dollar", - code = "USD", - image = "https://hatscripts.github.io/circle-flags/flags/us.svg", - precision = 2, - ), - onrampAvailable = true, - ), - OnrampCountryDTO( - name = "Europe Union", - code = "EU", - image = "https://hatscripts.github.io/circle-flags/flags/eu.svg", - alpha3 = "EUR", - continent = "", - defaultCurrency = OnrampCurrencyDTO( - name = "Euro", - code = "EUR", - image = "https://hatscripts.github.io/circle-flags/flags/eu.svg", - precision = 2, - ), - onrampAvailable = true, - ), - OnrampCountryDTO( - name = "Great Britain", - code = "GB", - image = "https://hatscripts.github.io/circle-flags/flags/gb.svg", - alpha3 = "GB", - continent = "", - defaultCurrency = OnrampCurrencyDTO( - name = "British Pound Sterling", - code = "GBP", - image = "https://hatscripts.github.io/circle-flags/flags/gb.svg", - precision = 2, - ), - onrampAvailable = true, - ), - OnrampCountryDTO( - name = "CANADA", - code = "CA", - image = "https://hatscripts.github.io/circle-flags/flags/ca.svg", - alpha3 = "CA", - continent = "", - defaultCurrency = OnrampCurrencyDTO( - name = "Canadian Dollar", - code = "CAD", - image = "https://hatscripts.github.io/circle-flags/flags/ca.svg", - precision = 2, - ), - onrampAvailable = true, - ), - OnrampCountryDTO( - name = "Hon Kong", - code = "HK", - image = "https://hatscripts.github.io/circle-flags/flags/hk.svg", - alpha3 = "HK", - continent = "", - defaultCurrency = OnrampCurrencyDTO( - name = "Hon Kong Dollar", - code = "HKD", - image = "https://hatscripts.github.io/circle-flags/flags/hk.svg", - precision = 2, - ), - onrampAvailable = true, - ), - OnrampCountryDTO( - name = "Australia", - code = "AU", - image = "https://hatscripts.github.io/circle-flags/flags/au.svg", - alpha3 = "AU", - continent = "", - defaultCurrency = OnrampCurrencyDTO( - name = "Australian Dollar", - code = "AUD", - image = "https://hatscripts.github.io/circle-flags/flags/au.svg", - precision = 2, - ), - onrampAvailable = true, - ), - ) - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 720e14ac0b..8b38ca983a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -11,7 +11,6 @@ import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.markets.TangemTechMarketsApi -import com.tangem.datasource.api.onramp.MockedOnrampApi import com.tangem.datasource.api.onramp.OnrampApi import com.tangem.datasource.api.stakekit.StakeKitApi import com.tangem.datasource.api.tangemTech.TangemTechApi @@ -99,24 +98,22 @@ internal object NetworkModule { @Provides @Singleton fun provideOnrampApi( - // @NetworkMoshi moshi: Moshi, - // @ApplicationContext context: Context, - // apiConfigsManager: ApiConfigsManager, - // appLogsStore: AppLogsStore, + @NetworkMoshi moshi: Moshi, + @ApplicationContext context: Context, + apiConfigsManager: ApiConfigsManager, + appLogsStore: AppLogsStore, ): OnrampApi { - // TODO: Remove when backend will be ready - [REDACTED_TASK_KEY] - return MockedOnrampApi() - // return createApi( - // id = ApiConfig.ID.Express, - // moshi = moshi, - // context = context, - // apiConfigsManager = apiConfigsManager, - // clientBuilder = { - // addInterceptor( - // NetworkLogsSaveInterceptor(appLogsStore), - // ) - // }, - // ) + return createApi( + id = ApiConfig.ID.Express, + moshi = moshi, + context = context, + apiConfigsManager = apiConfigsManager, + clientBuilder = { + addInterceptor( + NetworkLogsSaveInterceptor(appLogsStore), + ) + }, + ) } @Provides diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 10f76c5e66..60946f6ff4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -103,6 +103,8 @@ object PreferencesKeys { val SHOULD_SHOW_RING_PROMO_KEY by lazy { booleanPreferencesKey(name = "shouldShowRingPromo") } + val ONRAMP_DEFAULT_CURRENCY by lazy { stringPreferencesKey(name = "onrampDefaultCurrency") } + // region Permission fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission") diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index bdbb147344..55fcfc1077 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -4,15 +4,24 @@ import com.tangem.data.onramp.converters.CountryConverter import com.tangem.data.onramp.converters.CurrencyConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.onramp.OnrampApi +import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObject +import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull +import com.tangem.datasource.local.preferences.utils.storeObject import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.domain.onramp.model.OnrampCurrency import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext internal class DefaultOnrampRepository( private val onrampApi: OnrampApi, private val dispatchers: CoroutineDispatcherProvider, + private val appPreferencesStore: AppPreferencesStore, ) : OnrampRepository { private val currencyConverter = CurrencyConverter() @@ -29,4 +38,23 @@ internal class DefaultOnrampRepository( .getOrThrow() .map(countryConverter::convert) } + + override suspend fun saveDefaultCurrency(currency: OnrampCurrency) { + appPreferencesStore.storeObject( + key = PreferencesKeys.ONRAMP_DEFAULT_CURRENCY, + value = currencyConverter.convertBack(currency), + ) + } + + override suspend fun getDefaultCurrencySync(): OnrampCurrency? { + return appPreferencesStore + .getObjectSyncOrNull(PreferencesKeys.ONRAMP_DEFAULT_CURRENCY) + ?.let(currencyConverter::convert) + } + + override fun getDefaultCurrency(): Flow { + return appPreferencesStore + .getObject(PreferencesKeys.ONRAMP_DEFAULT_CURRENCY) + .map { it?.let(currencyConverter::convert) } + } } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/CurrencyConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/CurrencyConverter.kt index 01e8ee6309..4c37be2fc8 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/converters/CurrencyConverter.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/CurrencyConverter.kt @@ -2,9 +2,9 @@ package com.tangem.data.onramp.converters import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO import com.tangem.domain.onramp.model.OnrampCurrency -import com.tangem.utils.converter.Converter +import com.tangem.utils.converter.TwoWayConverter -internal class CurrencyConverter : Converter { +internal class CurrencyConverter : TwoWayConverter { override fun convert(value: OnrampCurrencyDTO) = OnrampCurrency( name = value.name, @@ -12,4 +12,11 @@ internal class CurrencyConverter : Converter image = value.image, precision = value.precision, ) + + override fun convertBack(value: OnrampCurrency): OnrampCurrencyDTO = OnrampCurrencyDTO( + name = value.name, + code = value.code, + image = value.image, + precision = value.precision, + ) } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt b/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt index c1b0d24e4b..d47543851a 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt @@ -2,6 +2,7 @@ package com.tangem.data.onramp.di import com.tangem.data.onramp.DefaultOnrampRepository import com.tangem.datasource.api.onramp.OnrampApi +import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -16,10 +17,15 @@ internal object OnrampDataModule { @Provides @Singleton - fun provideOnrampRepository(onrampApi: OnrampApi, dispatchers: CoroutineDispatcherProvider): OnrampRepository { + fun provideOnrampRepository( + onrampApi: OnrampApi, + dispatchers: CoroutineDispatcherProvider, + appPreferencesStore: AppPreferencesStore, + ): OnrampRepository { return DefaultOnrampRepository( onrampApi = onrampApi, dispatchers = dispatchers, + appPreferencesStore = appPreferencesStore, ) } } \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrencies.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrencies.kt new file mode 100644 index 0000000000..1af69cd6ef --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrencies.kt @@ -0,0 +1,3 @@ +package com.tangem.domain.onramp.model + +data class OnrampCurrencies(val populars: List, val others: List) \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt new file mode 100644 index 0000000000..b1206947db --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt @@ -0,0 +1,23 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampCurrencies +import com.tangem.domain.onramp.repositories.OnrampRepository + +class GetOnrampCurrenciesUseCase( + private val onrampRepository: OnrampRepository, +) { + + suspend operator fun invoke(): Either { + return Either.catch { + val currenciesList = onrampRepository.getCurrencies() + val (populars, others) = currenciesList.toSet() + .partition { popularFiatCodes.contains(it.code.uppercase()) } + OnrampCurrencies(populars = populars, others = others) + } + } + + private companion object { + val popularFiatCodes = setOf("USD", "EUR", "GBP", "CAD", "AUD", "HKD") + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt new file mode 100644 index 0000000000..e30213f5ab --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.onramp + +import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.domain.onramp.repositories.OnrampRepository + +class OnrampSaveDefaultCurrencyUseCase(private val onrampRepository: OnrampRepository) { + + suspend operator fun invoke(currency: OnrampCurrency) { + onrampRepository.saveDefaultCurrency(currency) + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt index 8b251a9d47..73c41ce3db 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt @@ -2,8 +2,12 @@ package com.tangem.domain.onramp.repositories import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.domain.onramp.model.OnrampCurrency +import kotlinx.coroutines.flow.Flow interface OnrampRepository { suspend fun getCurrencies(): List suspend fun getCountries(): List + suspend fun saveDefaultCurrency(currency: OnrampCurrency) + suspend fun getDefaultCurrencySync(): OnrampCurrency? + fun getDefaultCurrency(): Flow } \ No newline at end of file diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 1ff3b4c39c..be47292f09 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { implementation(projects.domain.balanceHiding.models) implementation(projects.domain.legacy) implementation(projects.domain.models) + implementation(projects.domain.onramp) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt index a55688ca57..9d3406858d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt @@ -46,7 +46,7 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor( override fun BottomSheet() { val state by model.state.collectAsStateWithLifecycle() val bottomSheet by bottomSheetSlot.subscribeAsState() - val bottomSheetConfig = remember { + val bottomSheetConfig = remember(key1 = this) { TangemBottomSheetConfig( isShow = true, onDismissRequest = ::dismiss, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt index b5e167f157..bb051a618e 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt @@ -2,6 +2,7 @@ package com.tangem.features.onramp.component.preview import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent @@ -31,11 +32,13 @@ internal class PreviewConfirmResidencyComponent( @Composable override fun BottomSheet() { val state by previewState.collectAsStateWithLifecycle() - val bottomSheetConfig = TangemBottomSheetConfig( - isShow = true, - onDismissRequest = ::dismiss, - content = TangemBottomSheetConfigContent.Empty, - ) + val bottomSheetConfig = remember(key1 = this) { + TangemBottomSheetConfig( + isShow = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } ConfirmResidencyBottomSheet( config = bottomSheetConfig, content = { modifier -> diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt index 9ac777a7b4..60c7d65ff5 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt @@ -31,7 +31,7 @@ internal class DefaultSelectCountryComponent @AssistedInject constructor( @Composable override fun BottomSheet() { val state by model.state.collectAsStateWithLifecycle() - val bottomSheetConfig = remember { + val bottomSheetConfig = remember(key1 = this) { TangemBottomSheetConfig( isShow = true, onDismissRequest = ::dismiss, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt index e91aa92aea..5956d4128d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -import timber.log.Timber import javax.inject.Inject internal class CountryListUMController @Inject constructor() { @@ -22,12 +21,10 @@ internal class CountryListUMController @Inject constructor() { ) fun update(transformer: UpdateCountryItemsTransformer) { - Timber.d("Applying ${transformer::class.simpleName}") _state.update(transformer::transform) } fun update(transformer: SearchBarUMTransformer) { - Timber.d("Applying ${transformer::class.simpleName}") _state.update { prevState -> val searchBarItem = prevState.getSearchBar() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt index 8af238ebf4..9b8e8b9f73 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt @@ -6,15 +6,16 @@ import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selectcountry.entity.CountriesListItemUM import com.tangem.features.onramp.selectcountry.entity.CountryItemState import com.tangem.features.onramp.selectcountry.entity.CountryListUM +import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.toImmutableList internal class UpdateCountryItemsTransformer( private val countries: List, private val onQueryChange: (String) -> Unit, private val onActiveChange: (Boolean) -> Unit, -) { +) : Transformer { - fun transform(prevState: CountryListUM): CountryListUM { + override fun transform(prevState: CountryListUM): CountryListUM { val searchBarItem = prevState.getSearchBar() ?: createSearchBarItem() return prevState.copy( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt new file mode 100644 index 0000000000..687df5d015 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt @@ -0,0 +1,56 @@ +package com.tangem.features.onramp.selectcurrency + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +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.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.features.onramp.selectcurrency.model.OnrampSelectCurrencyModel +import com.tangem.features.onramp.selectcurrency.ui.OnrampCurrencyList +import com.tangem.features.onramp.selectcurrency.ui.SelectCurrencyBottomSheet +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultSelectCurrencyComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted params: SelectCurrencyComponent.Params, +) : SelectCurrencyComponent, AppComponentContext by context { + + private val model: OnrampSelectCurrencyModel = getOrCreateModel(params) + + override fun dismiss() { + model.dismiss() + } + + @Composable + override fun BottomSheet() { + val state by model.state.collectAsStateWithLifecycle() + val bottomSheetConfig = remember(key1 = this) { + TangemBottomSheetConfig( + isShow = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } + SelectCurrencyBottomSheet( + config = bottomSheetConfig, + content = { + OnrampCurrencyList(modifier = Modifier.fillMaxSize(), state = state) + }, + ) + } + + @AssistedFactory + interface Factory : SelectCurrencyComponent.Factory { + override fun create( + context: AppComponentContext, + params: SelectCurrencyComponent.Params, + ): DefaultSelectCurrencyComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/SelectCurrencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/SelectCurrencyComponent.kt new file mode 100644 index 0000000000..a6c69ab8f3 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/SelectCurrencyComponent.kt @@ -0,0 +1,11 @@ +package com.tangem.features.onramp.selectcurrency + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent + +internal interface SelectCurrencyComponent : ComposableBottomSheetComponent { + + data class Params(val onDismiss: () -> Unit) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyComponentModule.kt new file mode 100644 index 0000000000..f8ccf16d35 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.selectcurrency.di + +import com.tangem.features.onramp.selectcurrency.DefaultSelectCurrencyComponent +import com.tangem.features.onramp.selectcurrency.SelectCurrencyComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampSelectCurrencyComponentModule { + + @Binds + @Singleton + fun bindSelectCurrencyComponentFactory( + factory: DefaultSelectCurrencyComponent.Factory, + ): SelectCurrencyComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt new file mode 100644 index 0000000000..3541b9cb45 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.selectcurrency.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.selectcurrency.model.OnrampSelectCurrencyModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampSelectCurrencyModelModule { + + @Binds + @IntoMap + @ClassKey(OnrampSelectCurrencyModel::class) + fun bindOnrampSelectCurrencyModel(model: OnrampSelectCurrencyModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesListUM.kt new file mode 100644 index 0000000000..91916eb2c4 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesListUM.kt @@ -0,0 +1,31 @@ +package com.tangem.features.onramp.selectcurrency.entity + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import kotlinx.collections.immutable.ImmutableList + +@Immutable +internal sealed interface CurrenciesListUM { + + val searchBarUM: SearchBarUM + + data class Loading( + override val searchBarUM: SearchBarUM, + val sections: ImmutableList>, + ) : CurrenciesListUM + + data class Error(override val searchBarUM: SearchBarUM, val onRetry: () -> Unit) : CurrenciesListUM + + data class Content( + override val searchBarUM: SearchBarUM, + val sections: ImmutableList>, + ) : CurrenciesListUM + + fun copySealed(searchBarUM: SearchBarUM = this.searchBarUM): CurrenciesListUM { + return when (this) { + is Loading -> this.copy(searchBarUM = searchBarUM) + is Error -> this.copy(searchBarUM = searchBarUM) + is Content -> this.copy(searchBarUM = searchBarUM) + } + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesSection.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesSection.kt new file mode 100644 index 0000000000..b15dffbba1 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesSection.kt @@ -0,0 +1,6 @@ +package com.tangem.features.onramp.selectcurrency.entity + +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.ImmutableList + +internal data class CurrenciesSection(val title: TextReference, val items: ImmutableList) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyItemState.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyItemState.kt new file mode 100644 index 0000000000..0e3c11fe59 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyItemState.kt @@ -0,0 +1,19 @@ +package com.tangem.features.onramp.selectcurrency.entity + +import androidx.compose.runtime.Immutable +import com.tangem.domain.onramp.model.OnrampCurrency + +@Immutable +internal sealed interface CurrencyItemState { + + /** Unique ID */ + val id: String + + data class Loading(override val id: String) : CurrencyItemState + + data class Content( + override val id: String, + val onrampCurrency: OnrampCurrency, + val onClick: () -> Unit, + ) : CurrencyItemState +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt new file mode 100644 index 0000000000..157e5a9133 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt @@ -0,0 +1,41 @@ +package com.tangem.features.onramp.selectcurrency.entity + +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.onramp.selectcurrency.entity.transformer.UpdateCurrencyItemsTransformer +import com.tangem.features.onramp.utils.SearchBarUMTransformer +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update + +internal class CurrencyListController(private val currencySearchBarUM: SearchBarUM) { + val state: StateFlow get() = _state.asStateFlow() + private val _state: MutableStateFlow = MutableStateFlow(value = getInitialState()) + + fun update(transformer: UpdateCurrencyItemsTransformer) { + _state.update(transformer::transform) + } + + fun update(transformer: SearchBarUMTransformer) { + _state.update { prevState -> + val searchBarUM = transformer.transform(prevState.searchBarUM) + prevState.copySealed(searchBarUM) + } + } + + private fun getInitialState(): CurrenciesListUM { + return CurrenciesListUM.Loading( + searchBarUM = currencySearchBarUM, + sections = listOf( + CurrenciesSection(stringReference("Popular fiats"), items = createLoadingItems("popular")), + CurrenciesSection(stringReference("Other currencies"), items = createLoadingItems("other")), + ).toImmutableList(), + ) + } + + private fun createLoadingItems(prefix: String, size: Int = 5): ImmutableList = + MutableList(size) { index -> CurrencyItemState.Loading("$prefix#$index") }.toImmutableList() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt new file mode 100644 index 0000000000..8e3782838b --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt @@ -0,0 +1,65 @@ +package com.tangem.features.onramp.selectcurrency.entity.transformer + +import arrow.core.Either +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.onramp.model.OnrampCurrencies +import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM +import com.tangem.features.onramp.selectcurrency.entity.CurrenciesSection +import com.tangem.features.onramp.selectcurrency.entity.CurrencyItemState +import com.tangem.utils.transformer.Transformer +import kotlinx.collections.immutable.toImmutableList + +internal class UpdateCurrencyItemsTransformer( + private val maybeCurrencies: Either, + private val query: String, + private val onRetry: () -> Unit, + private val onCurrencyClick: (OnrampCurrency) -> Unit, +) : Transformer { + + override fun transform(prevState: CurrenciesListUM): CurrenciesListUM { + return maybeCurrencies.fold( + ifLeft = { CurrenciesListUM.Error(searchBarUM = prevState.searchBarUM, onRetry = onRetry) }, + ifRight = { currencies -> + val populars = currencies.populars.filterByQuery() + val others = currencies.others.filterByQuery() + val sections = buildList { + if (populars.isNotEmpty()) { + add( + CurrenciesSection( + title = stringReference("Popular fiats"), + items = populars.map(::convertCurrencyToUiModel).toImmutableList(), + ), + ) + } + if (others.isNotEmpty()) { + add( + CurrenciesSection( + title = stringReference("Other currencies"), + items = others.map(::convertCurrencyToUiModel).toImmutableList(), + ), + ) + } + } + CurrenciesListUM.Content( + searchBarUM = prevState.searchBarUM, + sections = sections.toImmutableList(), + ) + }, + ) + } + + private fun List.filterByQuery(): List { + return filter { + it.code.lowercase().contains(query.lowercase()) || it.name.lowercase().contains(query.lowercase()) + } + } + + private fun convertCurrencyToUiModel(onrampCurrency: OnrampCurrency): CurrencyItemState.Content { + return CurrencyItemState.Content( + id = onrampCurrency.code, + onrampCurrency = onrampCurrency, + onClick = { onCurrencyClick(onrampCurrency) }, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt new file mode 100644 index 0000000000..b6668e3f40 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt @@ -0,0 +1,107 @@ +package com.tangem.features.onramp.selectcurrency.model + +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.onramp.GetOnrampCurrenciesUseCase +import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase +import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.selectcurrency.SelectCurrencyComponent +import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM +import com.tangem.features.onramp.selectcurrency.entity.CurrencyListController +import com.tangem.features.onramp.selectcurrency.entity.transformer.UpdateCurrencyItemsTransformer +import com.tangem.features.onramp.utils.SearchManager +import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer +import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ComponentScoped +internal class OnrampSelectCurrencyModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val searchManager: SearchManager, + private val getOnrampCurrenciesUseCase: GetOnrampCurrenciesUseCase, + private val saveDefaultCurrencyUseCase: OnrampSaveDefaultCurrencyUseCase, + paramsContainer: ParamsContainer, +) : Model() { + + val state: StateFlow get() = controller.state + + private val params: SelectCurrencyComponent.Params = paramsContainer.require() + private val controller = CurrencyListController(currencySearchBarUM = createSearchBarUM()) + private val refreshTrigger = MutableSharedFlow() + + init { + subscribeOnUpdateState() + } + + @OptIn(ExperimentalCoroutinesApi::class) + private fun subscribeOnUpdateState() { + combine( + flow = refreshTrigger.onStart { emit(Unit) }.flatMapLatest { flowOf(getOnrampCurrenciesUseCase.invoke()) }, + flow2 = searchManager.query, + ) { maybeCurrencies, query -> + UpdateCurrencyItemsTransformer( + maybeCurrencies = maybeCurrencies, + query = query, + onRetry = ::onRetry, + onCurrencyClick = ::saveDefaultCurrency, + ) + } + .onEach(controller::update) + .launchIn(modelScope) + } + + fun dismiss() { + params.onDismiss() + } + + private fun saveDefaultCurrency(currency: OnrampCurrency) { + modelScope.launch { + saveDefaultCurrencyUseCase.invoke(currency) + dismiss() + } + } + + private fun onRetry() { + modelScope.launch { + refreshTrigger.emit(Unit) + } + } + + private fun onSearchQueryChange(newQuery: String) { + val searchBarUM = controller.state.value.searchBarUM + if (searchBarUM.query == newQuery) return + + modelScope.launch { + controller.update(transformer = UpdateSearchQueryTransformer(newQuery)) + + searchManager.update(newQuery) + } + } + + private fun onSearchBarActiveChange(isActive: Boolean) { + controller.update( + transformer = UpdateSearchBarActiveStateTransformer( + isActive = isActive, + placeHolder = resourceReference(id = R.string.common_search), + ), + ) + } + + private fun createSearchBarUM(): SearchBarUM { + return SearchBarUM( + placeholderText = resourceReference(R.string.onramp_currency_search), + query = "", + onQueryChange = ::onSearchQueryChange, + isActive = false, + onActiveChange = ::onSearchBarActiveChange, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt new file mode 100644 index 0000000000..88dfc1d6d9 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt @@ -0,0 +1,145 @@ +package com.tangem.features.onramp.selectcurrency.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.util.fastForEach +import coil.compose.AsyncImage +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.TextShimmer +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.fields.SearchBar +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM +import com.tangem.features.onramp.selectcurrency.entity.CurrencyItemState + +@Composable +internal fun SelectCurrencyBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) { + TangemBottomSheet( + config = config, + content = { content() }, + ) +} + +@Composable +internal fun OnrampCurrencyList(state: CurrenciesListUM, modifier: Modifier = Modifier) { + LazyColumn( + modifier = modifier, + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), + ) { + item(key = "search_bar") { + SearchBar( + state = state.searchBarUM, + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing20), + ) + } + when (state) { + is CurrenciesListUM.Content -> currencyListContent(state = state) + is CurrenciesListUM.Error -> TODO() + is CurrenciesListUM.Loading -> currencyListLoading(state = state) + } + } +} + +private fun LazyListScope.currencyListLoading(state: CurrenciesListUM.Loading) { + state.sections.fastForEach { section -> + item(key = section.title.toString()) { GroupTitle(title = section.title.resolveReference()) } + items( + items = section.items, + key = { item -> item.id }, + contentType = { item -> item::class.java }, + itemContent = { CurrencyLoading(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing16)) }, + ) + } +} + +private fun LazyListScope.currencyListContent(state: CurrenciesListUM.Content) { + state.sections.fastForEach { section -> + item(key = section.title.toString()) { GroupTitle(title = section.title.resolveReference()) } + items( + items = section.items, + key = { item -> item.id }, + contentType = { item -> item::class.java }, + itemContent = { item -> + CurrencyContent( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = item.onClick) + .padding(vertical = TangemTheme.dimens.spacing16), + currency = item, + ) + }, + ) + } +} + +@Composable +private fun GroupTitle(title: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier, + text = title, + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) +} + +@Composable +private fun CurrencyContent(currency: CurrencyItemState.Content, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + ) { + AsyncImage( + modifier = Modifier + .size(TangemTheme.dimens.size36) + .clip(CircleShape), + model = currency.onrampCurrency.image, + contentDescription = null, + ) + Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2)) { + Text( + text = currency.onrampCurrency.code, + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + ) + Text( + text = currency.onrampCurrency.name, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ) + } + } +} + +@Composable +private fun CurrencyLoading(modifier: Modifier = Modifier) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + ) { + CircleShimmer(modifier = Modifier.size(TangemTheme.dimens.size36)) + Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) { + TextShimmer( + modifier = Modifier.width(width = TangemTheme.dimens.size70), + style = TangemTheme.typography.subtitle2, + ) + TextShimmer( + modifier = Modifier.width(width = TangemTheme.dimens.size52), + style = TangemTheme.typography.caption2, + ) + } + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt index b47bbe1810..a2f80e0f27 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt @@ -24,6 +24,7 @@ internal class SearchManager @Inject constructor() { suspend fun update(value: String) { coroutineScope { if (value.isEmpty()) { + jobHolder.cancel() _query.value = value } else { withDebounce(jobHolder) { _query.value = value } From 9c120b9914125cd941fc1f2e87cd67309b0209a1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Nov 2024 16:20:11 +0400 Subject: [PATCH 13/33] Updated on 2026-08-14 --- .../java/com/tangem/tap/di/ActivityModule.kt | 9 ++- .../tap/di/domain/TokensDomainModule.kt | 6 +- .../exchangeServices/DefaultRampManager.kt | 44 +++++++++-- core/datasource/build.gradle.kts | 1 + .../ExchangeServiceLoaderModule.kt | 18 +++++ .../swap/DefaultSwapServiceLoader.kt | 76 +++++++++++++++++++ .../exchangeservice/swap/SwapServiceLoader.kt | 21 +++++ .../tangem/data/tokens/di/TokensDataModule.kt | 22 ++---- .../repository/DefaultCurrenciesRepository.kt | 31 +------- .../DefaultMarketCryptoCurrencyRepository.kt | 33 -------- .../domain/exchange/RampStateManager.kt | 6 ++ .../tokens/GetCryptoCurrencyActionsUseCase.kt | 6 +- .../tokens/GetCurrencyWarningsUseCase.kt | 7 +- .../MarketCryptoCurrencyRepository.kt | 12 --- 14 files changed, 183 insertions(+), 109 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/exchangeservice/ExchangeServiceLoaderModule.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/SwapServiceLoader.kt delete mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt delete mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/MarketCryptoCurrencyRepository.kt diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index 9e18be5306..f5eb1f355e 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -1,17 +1,18 @@ package com.tangem.tap.di +import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase -import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.network.exchangeServices.DefaultRampManager import com.tangem.tap.proxy.AppStateHolder import com.tangem.utils.Provider +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -43,13 +44,15 @@ internal object ActivityModule { @Singleton fun provideDefaultRampManager( appStateHolder: AppStateHolder, - marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, + swapServiceLoader: SwapServiceLoader, + dispatchers: CoroutineDispatcherProvider, ): RampStateManager { return DefaultRampManager( exchangeService = appStateHolder.exchangeService, buyService = Provider { requireNotNull(appStateHolder.buyService) }, sellService = Provider { requireNotNull(appStateHolder.sellService) }, - marketsCryptoCurrencyRepository = marketCryptoCurrencyRepository, + swapServiceLoader = swapServiceLoader, + dispatchers = dispatchers, ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 58c3b2a490..9f805a3571 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -118,26 +118,26 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, - marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, swapRepository: SwapRepository, currencyChecksRepository: CurrencyChecksRepository, showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, promoRepository: PromoRepository, stakingRepository: StakingRepository, dispatchers: CoroutineDispatcherProvider, + rampStateManager: RampStateManager, ): GetCurrencyWarningsUseCase { return GetCurrencyWarningsUseCase( walletManagersFacade = walletManagersFacade, currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, - marketCryptoCurrencyRepository = marketCryptoCurrencyRepository, currencyChecksRepository = currencyChecksRepository, swapRepository = swapRepository, showSwapPromoTokenUseCase = showSwapPromoTokenUseCase, promoRepository = promoRepository, stakingRepository = stakingRepository, dispatchers = dispatchers, + rampStateManager = rampStateManager, ) } @@ -215,7 +215,6 @@ internal object TokensDomainModule { fun provideGetCryptoCurrencyActionsUseCase( rampStateManager: RampStateManager, walletManagersFacade: WalletManagersFacade, - marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, @@ -225,7 +224,6 @@ internal object TokensDomainModule { return GetCryptoCurrencyActionsUseCase( rampManager = rampStateManager, walletManagersFacade = walletManagersFacade, - marketCryptoCurrencyRepository = marketCryptoCurrencyRepository, currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 2ce18c2fe0..b512d32ba7 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -1,25 +1,29 @@ package com.tangem.tap.network.exchangeServices +import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE +import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.Provider +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext -class DefaultRampManager( +internal class DefaultRampManager( private val exchangeService: ExchangeService?, private val buyService: Provider, private val sellService: Provider, - private val marketsCryptoCurrencyRepository: MarketCryptoCurrencyRepository, + private val swapServiceLoader: SwapServiceLoader, + private val dispatchers: CoroutineDispatcherProvider, ) : RampStateManager { private val cryptoCurrencyConverter = CryptoCurrencyConverter() override fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean { return exchangeService?.availableForBuy( - scanResponse, + scanResponse = scanResponse, currency = cryptoCurrencyConverter.convertBack(cryptoCurrency), ) ?: false } @@ -31,14 +35,44 @@ class DefaultRampManager( } override suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { - return marketsCryptoCurrencyRepository.isExchangeable(userWalletId, cryptoCurrency) + return getExchangeableFlag(userWalletId, cryptoCurrency) && !cryptoCurrency.isCustom } override fun getBuyInitializationStatus(): Flow { return buyService.invoke().initializationStatus } + override suspend fun fetchBuyServiceData() { + withContext(dispatchers.io) { + buyService.invoke().update() + } + } + override fun getSellInitializationStatus(): Flow { return sellService.invoke().initializationStatus } + + override suspend fun fetchSellServiceData() { + withContext(dispatchers.io) { + sellService.invoke().update() + } + } + + override fun getSwapInitializationStatus(userWalletId: UserWalletId): Flow { + return swapServiceLoader.getInitializationStatus(userWalletId) + } + + private suspend fun getExchangeableFlag(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { + return withContext(dispatchers.io) { + val contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress + ?: EMPTY_CONTRACT_ADDRESS_VALUE + + val asset = swapServiceLoader.getInitializationStatus(userWalletId).value.getOrNull()?.find { + it.network == cryptoCurrency.network.backendId && + it.contractAddress.equals(contractAddress, ignoreCase = true) + } + + asset?.exchangeAvailable ?: false + } + } } \ No newline at end of file diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 494f2c12e6..273078fac5 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { implementation(projects.core.utils) implementation(projects.libs.auth) implementation(projects.domain.appTheme.models) + implementation(projects.domain.core) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) implementation(projects.domain.balanceHiding.models) diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/exchangeservice/ExchangeServiceLoaderModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/exchangeservice/ExchangeServiceLoaderModule.kt new file mode 100644 index 0000000000..03ab7fa98a --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/exchangeservice/ExchangeServiceLoaderModule.kt @@ -0,0 +1,18 @@ +package com.tangem.datasource.di.exchangeservice + +import com.tangem.datasource.exchangeservice.swap.DefaultSwapServiceLoader +import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface ExchangeServiceLoaderModule { + + @Binds + @Singleton + fun bindSwapServiceLoader(defaultSwapServiceLoader: DefaultSwapServiceLoader): SwapServiceLoader +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt new file mode 100644 index 0000000000..d7e0fbd410 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt @@ -0,0 +1,76 @@ +package com.tangem.datasource.exchangeservice.swap + +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.express.TangemExpressApi +import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE +import com.tangem.datasource.api.express.models.request.AssetsRequestBody +import com.tangem.datasource.api.express.models.request.LeastTokenInfo +import com.tangem.datasource.api.express.models.response.Asset +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.local.token.ExpressAssetsStore +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.core.utils.lceContent +import com.tangem.domain.core.utils.lceError +import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import timber.log.Timber +import javax.inject.Inject + +typealias InitializationStatusFlow = MutableStateFlow>> + +/** + * Default implementation of [SwapServiceLoader] + * + * @property tangemExpressApi express api + * @property expressAssetsStore local storage + * +[REDACTED_AUTHOR] + */ +internal class DefaultSwapServiceLoader @Inject constructor( + private val tangemExpressApi: TangemExpressApi, + private val expressAssetsStore: ExpressAssetsStore, +) : SwapServiceLoader { + + private val initializationStatuses: Map = mapOf() + + override suspend fun update(userWalletId: UserWalletId, userTokens: UserTokensResponse) { + val initializationStatus = getInitializationStatusInternal(userWalletId) + + initializationStatus.update { lceLoading() } + + try { + val tokensList = userTokens.tokens.map { + LeastTokenInfo( + contractAddress = it.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, + network = it.networkId, + ) + } + + if (tokensList.isNotEmpty()) { + val response = tangemExpressApi.getAssets( + body = AssetsRequestBody(tokensList = tokensList), + ).getOrThrow() + + expressAssetsStore.store(userWalletId, response) + + initializationStatus.update { response.lceContent() } + } + } catch (e: Throwable) { + initializationStatus.update { e.lceError() } + Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}") + } + } + + override fun getInitializationStatus(userWalletId: UserWalletId): InitializationStatusFlow { + return getInitializationStatusInternal(userWalletId) + } + + private fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow { + return initializationStatuses.getOrDefault( + key = userWalletId, + defaultValue = MutableStateFlow(value = lceLoading()), + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/SwapServiceLoader.kt b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/SwapServiceLoader.kt new file mode 100644 index 0000000000..0bee5e6d0b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/SwapServiceLoader.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.exchangeservice.swap + +import com.tangem.datasource.api.express.models.response.Asset +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.StateFlow + +/** + * Swap service loader + * +[REDACTED_AUTHOR] + */ +interface SwapServiceLoader { + + /** Update service using [userWalletId] and [userTokens] */ + suspend fun update(userWalletId: UserWalletId, userTokens: UserTokensResponse) + + /** Get initialization status by [userWalletId] */ + fun getInitializationStatus(userWalletId: UserWalletId): StateFlow>> +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 3e5c70b106..463d11f07a 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -2,12 +2,11 @@ package com.tangem.data.tokens.di import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.tokens.repository.* -import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader import com.tangem.datasource.local.network.NetworksStatusesStore import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.quote.QuotesStore -import com.tangem.datasource.local.token.ExpressAssetsStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.tokens.repository.* import com.tangem.domain.walletmanager.WalletManagersFacade @@ -26,23 +25,21 @@ internal object TokensDataModule { @Singleton fun provideCurrenciesRepository( tangemTechApi: TangemTechApi, - tangemExpressApi: TangemExpressApi, appPreferencesStore: AppPreferencesStore, userWalletsStore: UserWalletsStore, walletManagersFacade: WalletManagersFacade, - expressAssetsStore: ExpressAssetsStore, cacheRegistry: CacheRegistry, dispatchers: CoroutineDispatcherProvider, + swapServiceLoader: SwapServiceLoader, ): CurrenciesRepository { return DefaultCurrenciesRepository( tangemTechApi = tangemTechApi, - tangemExpressApi = tangemExpressApi, - appPreferencesStore = appPreferencesStore, - walletManagersFacade = walletManagersFacade, userWalletsStore = userWalletsStore, - expressAssetsStore = expressAssetsStore, + walletManagersFacade = walletManagersFacade, cacheRegistry = cacheRegistry, + appPreferencesStore = appPreferencesStore, dispatchers = dispatchers, + swapServiceLoader = swapServiceLoader, ) } @@ -84,15 +81,6 @@ internal object TokensDataModule { ) } - @Provides - @Singleton - fun provideDefaultMarketCoinsRepository( - expressAssetsStore: ExpressAssetsStore, - coroutineDispatcherProvider: CoroutineDispatcherProvider, - ): MarketCryptoCurrencyRepository { - return DefaultMarketCryptoCurrencyRepository(expressAssetsStore, coroutineDispatcherProvider) - } - @Provides @Singleton fun provideCurrencyChecksRepository(walletManagersFacade: WalletManagersFacade): CurrencyChecksRepository { diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index a739407505..31282f6f34 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -12,18 +12,14 @@ import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow -import com.tangem.datasource.api.express.TangemExpressApi -import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE -import com.tangem.datasource.api.express.models.request.AssetsRequestBody -import com.tangem.datasource.api.express.models.request.LeastTokenInfo import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObject import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.preferences.utils.storeObject -import com.tangem.datasource.local.token.ExpressAssetsStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.core.error.DataError import com.tangem.domain.demo.DemoConfig @@ -46,12 +42,11 @@ import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency @Suppress("LargeClass", "LongParameterList", "TooManyFunctions") internal class DefaultCurrenciesRepository( private val tangemTechApi: TangemTechApi, - private val tangemExpressApi: TangemExpressApi, private val userWalletsStore: UserWalletsStore, private val walletManagersFacade: WalletManagersFacade, - private val expressAssetsStore: ExpressAssetsStore, private val cacheRegistry: CacheRegistry, private val appPreferencesStore: AppPreferencesStore, + private val swapServiceLoader: SwapServiceLoader, private val dispatchers: CoroutineDispatcherProvider, ) : CurrenciesRepository { @@ -565,27 +560,7 @@ internal class DefaultCurrenciesRepository( userWalletId: UserWalletId, userTokens: UserTokensResponse, ) { - try { - val tokensList = userTokens.tokens - .map { - LeastTokenInfo( - contractAddress = it.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, - network = it.networkId, - ) - } - - if (tokensList.isNotEmpty()) { - val response = tangemExpressApi.getAssets( - AssetsRequestBody( - tokensList = tokensList, - ), - ) - - expressAssetsStore.store(userWalletId, response.getOrThrow()) - } - } catch (e: Throwable) { - Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}") - } + swapServiceLoader.update(userWalletId, userTokens) } private suspend fun handleFetchTokensError(userWallet: UserWallet, e: ApiResponseError): UserTokensResponse { diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt deleted file mode 100644 index 83f30079f3..0000000000 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.data.tokens.repository - -import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE -import com.tangem.datasource.local.token.ExpressAssetsStore -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext - -class DefaultMarketCryptoCurrencyRepository( - private val expressAssetsStore: ExpressAssetsStore, - private val dispatchers: CoroutineDispatcherProvider, -) : MarketCryptoCurrencyRepository { - - override suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { - return getExchangeableFlag(userWalletId, cryptoCurrency) && !cryptoCurrency.isCustom - } - - private suspend fun getExchangeableFlag(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { - return withContext(dispatchers.io) { - val contractAddress = - (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE - - val asset = expressAssetsStore.getSyncOrNull(userWalletId)?.find { - it.network == cryptoCurrency.network.backendId && - it.contractAddress.equals(contractAddress, ignoreCase = true) - } - - asset?.exchangeAvailable ?: false - } - } -} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index 423ecdc390..512be7ca64 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -17,7 +17,13 @@ interface RampStateManager { suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean + suspend fun fetchBuyServiceData() + fun getBuyInitializationStatus(): Flow> + suspend fun fetchSellServiceData() + fun getSellInitializationStatus(): Flow> + + fun getSwapInitializationStatus(userWalletId: UserWalletId): Flow> } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index b69ef4f341..ff6b9ec8d0 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -7,7 +7,6 @@ import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.walletmanager.WalletManagersFacade @@ -26,7 +25,6 @@ import kotlinx.coroutines.flow.* class GetCryptoCurrencyActionsUseCase( private val rampManager: RampStateManager, private val walletManagersFacade: WalletManagersFacade, - private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, @@ -168,7 +166,7 @@ class GetCryptoCurrencyActionsUseCase( // swap if (userWallet.isMultiCurrency) { if ( - marketCryptoCurrencyRepository.isExchangeable(userWallet.walletId, cryptoCurrency) && + rampManager.availableForSwap(userWallet.walletId, cryptoCurrency) && cryptoCurrencyStatus.value !is CryptoCurrencyStatus.NoQuote ) { activeList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.None)) @@ -300,7 +298,7 @@ class GetCryptoCurrencyActionsUseCase( return networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty() } - private suspend fun isStakingAvailable(userWallet: UserWallet, cryptoCurrency: CryptoCurrency): Boolean { + private fun isStakingAvailable(userWallet: UserWallet, cryptoCurrency: CryptoCurrency): Boolean { return stakingRepository.getStakingAvailability( userWalletId = userWallet.walletId, cryptoCurrency = cryptoCurrency, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index fc77aa776b..9118e3098d 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens +import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.model.* @@ -26,7 +27,7 @@ class GetCurrencyWarningsUseCase( private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, private val swapRepository: SwapRepository, - private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, + private val rampStateManager: RampStateManager, private val stakingRepository: StakingRepository, private val promoRepository: PromoRepository, private val showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, @@ -90,10 +91,10 @@ class GetCurrencyWarningsUseCase( val cryptoStatuses = operations.getCurrenciesStatusesSync() val promoBanner = promoRepository.getOkxPromoBanner() return combine( - showSwapPromoTokenUseCase() + flow = showSwapPromoTokenUseCase() .conflate() .distinctUntilChanged(), - flowOf(marketCryptoCurrencyRepository.isExchangeable(userWalletId, currency)) + flow2 = flowOf(rampStateManager.availableForSwap(userWalletId, currency)) .conflate() .distinctUntilChanged(), ) { shouldShowSwapPromo, isExchangeable -> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/MarketCryptoCurrencyRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/MarketCryptoCurrencyRepository.kt deleted file mode 100644 index 0e0d223a53..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/MarketCryptoCurrencyRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.domain.tokens.repository - -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.wallets.models.UserWalletId - -/** - * MarketCryptoCurrencyRepository works with data from Tangem coins backend, CoinMarketCap etc - */ -interface MarketCryptoCurrencyRepository { - - suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean -} \ No newline at end of file From 3d5a28e435f65b0cd0dbcc76c8c6a87e7c296c0c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Nov 2024 19:02:35 +0400 Subject: [PATCH 14/33] Updated on 2026-08-14 --- .../swap/DefaultSwapServiceLoader.kt | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt index d7e0fbd410..20044793fe 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt @@ -13,8 +13,10 @@ import com.tangem.domain.core.utils.lceContent import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject @@ -31,35 +33,38 @@ typealias InitializationStatusFlow = MutableStateFlow internal class DefaultSwapServiceLoader @Inject constructor( private val tangemExpressApi: TangemExpressApi, private val expressAssetsStore: ExpressAssetsStore, + private val dispatchers: CoroutineDispatcherProvider, ) : SwapServiceLoader { private val initializationStatuses: Map = mapOf() override suspend fun update(userWalletId: UserWalletId, userTokens: UserTokensResponse) { - val initializationStatus = getInitializationStatusInternal(userWalletId) + withContext(dispatchers.io) { + val initializationStatus = getInitializationStatusInternal(userWalletId) - initializationStatus.update { lceLoading() } + initializationStatus.update { lceLoading() } - try { - val tokensList = userTokens.tokens.map { - LeastTokenInfo( - contractAddress = it.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, - network = it.networkId, - ) + try { + val tokensList = userTokens.tokens.map { + LeastTokenInfo( + contractAddress = it.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE, + network = it.networkId, + ) + } + + if (tokensList.isNotEmpty()) { + val response = tangemExpressApi.getAssets( + body = AssetsRequestBody(tokensList = tokensList), + ).getOrThrow() + + expressAssetsStore.store(userWalletId, response) + + initializationStatus.update { response.lceContent() } + } + } catch (e: Throwable) { + initializationStatus.update { e.lceError() } + Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}") } - - if (tokensList.isNotEmpty()) { - val response = tangemExpressApi.getAssets( - body = AssetsRequestBody(tokensList = tokensList), - ).getOrThrow() - - expressAssetsStore.store(userWalletId, response) - - initializationStatus.update { response.lceContent() } - } - } catch (e: Throwable) { - initializationStatus.update { e.lceError() } - Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}") } } From 75e794abdf679a80d59b1f4008da9d11965da586 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Nov 2024 19:46:17 +0400 Subject: [PATCH 15/33] Updated on 2026-08-14 --- .../common/preview/WalletScreenPreviewData.kt | 1 + .../wallet/state/model/WalletState.kt | 28 ++++--- .../model/holder/LockedWalletStateHolder.kt | 4 + .../transformers/AddWalletTransformer.kt | 3 + .../InitializeWalletsTransformer.kt | 21 +++++- .../ReinitializeWalletTransformer.kt | 8 +- .../transformers/UnlockWalletTransformer.kt | 8 +- .../state/utils/WalletLoadingStateFactory.kt | 14 ++++ .../presentation/wallet/ui/WalletScreen.kt | 25 +++++-- .../components/common/WalletActionButtons.kt | 74 +++++++++++++++++++ .../SingleCurrencyControlButtons.kt | 37 ---------- .../wallet/viewmodels/WalletViewModel.kt | 6 ++ 12 files changed, 171 insertions(+), 58 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletActionButtons.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt index a871134063..97ebeee8a3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt @@ -119,6 +119,7 @@ internal object WalletScreenPreviewData { onRefresh = {}, ), walletCardState = miltiUnreachableCard, + buttons = persistentListOf(buyButton), warnings = persistentListOf( WalletNotification.Warning.SomeNetworksUnreachable, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt index 748fb7136c..6a23ccc059 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt @@ -24,6 +24,7 @@ internal sealed interface WalletState : WalletStateHolder { data class Content( override val pullToRefreshConfig: PullToRefreshConfig, override val walletCardState: WalletCardState, + override val buttons: PersistentList, override val warnings: ImmutableList, override val bottomSheetConfig: TangemBottomSheetConfig?, override val tokensListState: WalletTokensListState, @@ -31,13 +32,15 @@ internal sealed interface WalletState : WalletStateHolder { data class Locked( override val walletCardState: WalletCardState, + override val buttons: PersistentList, override val bottomSheetConfig: TangemBottomSheetConfig?, val onUnlockNotificationClick: () -> Unit, ) : MultiCurrency(), WalletStateHolder by LockedWalletStateHolder( - walletCardState, - bottomSheetConfig, - onUnlockNotificationClick, + walletCardState = walletCardState, + buttons = buttons, + bottomSheetConfig = bottomSheetConfig, + onUnlockNotificationClick = onUnlockNotificationClick, ) { override val tokensListState = WalletTokensListState.ContentState.Locked @@ -46,7 +49,6 @@ internal sealed interface WalletState : WalletStateHolder { sealed class SingleCurrency : WalletState, TxHistoryStateHolder { - abstract val buttons: PersistentList abstract val marketPriceBlockState: MarketPriceBlockState? data class Content( @@ -68,9 +70,10 @@ internal sealed interface WalletState : WalletStateHolder { ) : SingleCurrency(), TxHistoryStateHolder by LockedTxHistoryStateHolder(onExploreClick), WalletStateHolder by LockedWalletStateHolder( - walletCardState, - bottomSheetConfig, - onUnlockNotificationClick, + walletCardState = walletCardState, + buttons = buttons, + bottomSheetConfig = bottomSheetConfig, + onUnlockNotificationClick = onUnlockNotificationClick, ) { override val marketPriceBlockState: MarketPriceBlockState? = null @@ -84,6 +87,7 @@ internal sealed interface WalletState : WalletStateHolder { data class Content( override val pullToRefreshConfig: PullToRefreshConfig, override val walletCardState: WalletCardState, + override val buttons: PersistentList, override val warnings: ImmutableList, override val bottomSheetConfig: TangemBottomSheetConfig?, override val balancesAndLimitBlockState: BalancesAndLimitsBlockState, @@ -93,15 +97,17 @@ internal sealed interface WalletState : WalletStateHolder { data class Locked( override val walletCardState: WalletCardState, - val onUnlockNotificationClick: () -> Unit, + override val buttons: PersistentList, override val bottomSheetConfig: TangemBottomSheetConfig?, + val onUnlockNotificationClick: () -> Unit, val onExploreClick: () -> Unit, ) : Visa(), TxHistoryStateHolder by LockedTxHistoryStateHolder(onExploreClick), WalletStateHolder by LockedWalletStateHolder( - walletCardState, - bottomSheetConfig, - onUnlockNotificationClick, + walletCardState = walletCardState, + buttons = buttons, + bottomSheetConfig = bottomSheetConfig, + onUnlockNotificationClick = onUnlockNotificationClick, ) { override val balancesAndLimitBlockState: BalancesAndLimitsBlockState? = null diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt index 518723c898..4211ef693c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/holder/LockedWalletStateHolder.kt @@ -3,20 +3,24 @@ package com.tangem.feature.wallet.presentation.wallet.state.model.holder import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf internal interface WalletStateHolder { val pullToRefreshConfig: PullToRefreshConfig val walletCardState: WalletCardState + val buttons: PersistentList val warnings: ImmutableList val bottomSheetConfig: TangemBottomSheetConfig? } internal class LockedWalletStateHolder( override val walletCardState: WalletCardState, + override val buttons: PersistentList, override val bottomSheetConfig: TangemBottomSheetConfig?, onUnlockNotificationClick: () -> Unit, ) : WalletStateHolder { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt index 3f2da0d9a7..86e534351d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/AddWalletTransformer.kt @@ -5,18 +5,21 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlinx.collections.immutable.toImmutableList internal class AddWalletTransformer( private val userWallet: UserWallet, private val clickIntents: WalletClickIntents, private val walletImageResolver: WalletImageResolver, + private val walletFeatureToggles: WalletFeatureToggles, ) : WalletScreenStateTransformer { private val walletLoadingStateFactory by lazy { WalletLoadingStateFactory( clickIntents = clickIntents, walletImageResolver = walletImageResolver, + walletFeatureToggles = walletFeatureToggles, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt index 96d978a00f..0c85aed9cd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/InitializeWalletsTransformer.kt @@ -7,6 +7,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.* import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory import com.tangem.feature.wallet.presentation.wallet.state.utils.createStateByWalletType import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -16,10 +17,15 @@ internal class InitializeWalletsTransformer( private val wallets: List, private val clickIntents: WalletClickIntents, private val walletImageResolver: WalletImageResolver, + private val walletFeatureToggles: WalletFeatureToggles, ) : WalletScreenStateTransformer { private val walletLoadingStateFactory by lazy { - WalletLoadingStateFactory(clickIntents = clickIntents, walletImageResolver = walletImageResolver) + WalletLoadingStateFactory( + clickIntents = clickIntents, + walletImageResolver = walletImageResolver, + walletFeatureToggles = walletFeatureToggles, + ) } override fun transform(prevState: WalletScreenState): WalletScreenState { @@ -52,6 +58,7 @@ internal class InitializeWalletsTransformer( multiCurrencyCreator = { WalletState.MultiCurrency.Locked( walletCardState = userWallet.toLockedWalletCardState(), + buttons = createMultiWalletDisabledButtons(), bottomSheetConfig = null, onUnlockNotificationClick = clickIntents::onOpenUnlockWalletsBottomSheetClick, ) @@ -68,6 +75,7 @@ internal class InitializeWalletsTransformer( visaWalletCreator = { WalletState.Visa.Locked( walletCardState = userWallet.toLockedWalletCardState(), + buttons = createMultiWalletDisabledButtons(), bottomSheetConfig = null, onUnlockNotificationClick = clickIntents::onOpenUnlockWalletsBottomSheetClick, onExploreClick = clickIntents::onExploreClick, @@ -87,6 +95,17 @@ internal class InitializeWalletsTransformer( ) } + private fun createMultiWalletDisabledButtons(): PersistentList { + if (!walletFeatureToggles.isMainActionButtonsEnabled) return persistentListOf() + + return persistentListOf( + WalletManageButton.Receive(enabled = false, dimContent = false, onClick = {}, onLongClick = null), + WalletManageButton.Send(enabled = false, dimContent = false, onClick = {}), + WalletManageButton.Buy(enabled = false, dimContent = false, onClick = {}), + WalletManageButton.Sell(enabled = false, dimContent = false, onClick = {}), + ) + } + private fun createDisabledButtons(): PersistentList { return persistentListOf( WalletManageButton.Receive(enabled = false, dimContent = false, onClick = {}, onLongClick = null), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt index f083b489f0..45e0d62979 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/ReinitializeWalletTransformer.kt @@ -6,6 +6,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlinx.collections.immutable.toImmutableList /** @@ -22,10 +23,15 @@ internal class ReinitializeWalletTransformer( private val newUserWallet: UserWallet, private val clickIntents: WalletClickIntents, private val walletImageResolver: WalletImageResolver, + private val walletFeatureToggles: WalletFeatureToggles, ) : WalletScreenStateTransformer { private val walletLoadingStateFactory by lazy { - WalletLoadingStateFactory(clickIntents = clickIntents, walletImageResolver = walletImageResolver) + WalletLoadingStateFactory( + clickIntents = clickIntents, + walletImageResolver = walletImageResolver, + walletFeatureToggles = walletFeatureToggles, + ) } override fun transform(prevState: WalletScreenState): WalletScreenState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt index 7da90fd1ec..8d4c837f82 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UnlockWalletTransformer.kt @@ -7,6 +7,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenSta import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlinx.collections.immutable.toImmutableList import timber.log.Timber @@ -14,10 +15,15 @@ internal class UnlockWalletTransformer( private val unlockedWallets: List, private val clickIntents: WalletClickIntents, private val walletImageResolver: WalletImageResolver, + private val walletFeatureToggles: WalletFeatureToggles, ) : WalletScreenStateTransformer { private val walletLoadingStateFactory by lazy { - WalletLoadingStateFactory(clickIntents = clickIntents, walletImageResolver = walletImageResolver) + WalletLoadingStateFactory( + clickIntents = clickIntents, + walletImageResolver = walletImageResolver, + walletFeatureToggles = walletFeatureToggles, + ) } override fun transform(prevState: WalletScreenState): WalletScreenState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt index 28ce2908d0..2b622d7571 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt @@ -9,6 +9,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfo import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.state.model.* import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow @@ -21,6 +22,7 @@ import kotlinx.coroutines.flow.MutableStateFlow internal class WalletLoadingStateFactory( private val clickIntents: WalletClickIntents, private val walletImageResolver: WalletImageResolver, + private val walletFeatureToggles: WalletFeatureToggles, ) { fun create(userWallet: UserWallet): WalletState { @@ -35,6 +37,7 @@ internal class WalletLoadingStateFactory( return WalletState.MultiCurrency.Content( pullToRefreshConfig = createPullToRefreshConfig(), walletCardState = userWallet.toLoadingWalletCardState(), + buttons = createMultiWalletActions(), warnings = persistentListOf(), bottomSheetConfig = null, tokensListState = WalletTokensListState.ContentState.Loading, @@ -62,6 +65,7 @@ internal class WalletLoadingStateFactory( return WalletState.Visa.Content( pullToRefreshConfig = createPullToRefreshConfig(), walletCardState = userWallet.toLoadingWalletCardState(), + buttons = createMultiWalletActions(), warnings = persistentListOf(), bottomSheetConfig = null, balancesAndLimitBlockState = BalancesAndLimitsBlockState.Loading, @@ -89,6 +93,16 @@ internal class WalletLoadingStateFactory( ) } + private fun createMultiWalletActions(): PersistentList { + if (!walletFeatureToggles.isMainActionButtonsEnabled) return persistentListOf() + + return persistentListOf( + WalletManageButton.Buy(enabled = false, dimContent = true, onClick = { }), + WalletManageButton.Swap(enabled = false, dimContent = true, onClick = { }), + WalletManageButton.Sell(enabled = false, dimContent = true, onClick = { }), + ) + } + private fun createDimmedButtons(): PersistentList { return persistentListOf( WalletManageButton.Receive(enabled = true, dimContent = true, onClick = {}, onLongClick = null), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 8dc7d7fd8f..676e74e6da 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -75,8 +75,8 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.PushNotificat import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList import com.tangem.feature.wallet.presentation.wallet.ui.components.common.* +import com.tangem.feature.wallet.presentation.wallet.ui.components.common.actions import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton -import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.controlButtons import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.BalancesAndLimitsBottomSheet import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.VisaTxDetailsBottomSheet @@ -187,12 +187,23 @@ private fun WalletContent( ) } - (selectedWallet as? WalletState.SingleCurrency)?.let { - controlButtons( - configs = it.buttons, - selectedWalletIndex = selectedWalletIndex, - modifier = movableItemModifier.padding(top = betweenItemsPadding), - ) + when (selectedWallet) { + is WalletState.MultiCurrency, + is WalletState.Visa, + -> { + actions( + actions = selectedWallet.buttons, + selectedWalletIndex = selectedWalletIndex, + modifier = movableItemModifier.padding(top = betweenItemsPadding), + ) + } + is WalletState.SingleCurrency -> { + lazyActions( + actions = selectedWallet.buttons, + selectedWalletIndex = selectedWalletIndex, + modifier = movableItemModifier.padding(top = betweenItemsPadding), + ) + } } notifications(configs = selectedWallet.warnings, modifier = itemModifier) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletActionButtons.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletActionButtons.kt new file mode 100644 index 0000000000..31a46d0a6a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletActionButtons.kt @@ -0,0 +1,74 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components.common + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.runtime.key +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.buttons.HorizontalActionChips +import com.tangem.core.ui.components.buttons.actions.ActionButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton +import com.tangem.feature.wallet.presentation.wallet.ui.components.fastForEach +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + +private const val ACTIONS_CONTENT_TYPE = "Actions" + +/** + * Actions in scrollable container. Like, "Buy", "Sell", etc + * + * @param actions list of buttons + * @param selectedWalletIndex selected wallet index for creating a unique key + * @param modifier modifier + * +[REDACTED_AUTHOR] + */ +internal fun LazyListScope.lazyActions( + actions: ImmutableList, + selectedWalletIndex: Int, + modifier: Modifier = Modifier, +) { + item(key = ACTIONS_CONTENT_TYPE + selectedWalletIndex, contentType = ACTIONS_CONTENT_TYPE) { + HorizontalActionChips( + buttons = actions.map(WalletManageButton::config).toImmutableList(), + modifier = modifier.animateItem(), + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), + ) + } +} + +/** + * Action buttons. Like, "Buy", "Sell", etc + * + * @param actions list of buttons + * @param selectedWalletIndex selected wallet index for creating a unique key + * @param modifier modifier + * +[REDACTED_AUTHOR] + */ +internal fun LazyListScope.actions( + actions: ImmutableList, + selectedWalletIndex: Int, + modifier: Modifier = Modifier, +) { + if (actions.isEmpty()) return + + item(key = ACTIONS_CONTENT_TYPE + selectedWalletIndex, contentType = ACTIONS_CONTENT_TYPE) { + Row( + modifier = modifier + .padding(horizontal = 16.dp) + .fillMaxWidth() + .animateItem(), + horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8), + verticalAlignment = Alignment.CenterVertically, + ) { + actions.fastForEach { + key(it::class.java) { + ActionButton(config = it.config, modifier = Modifier.weight(1f)) + } + } + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt deleted file mode 100644 index 4b73b51b81..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/SingleCurrencyControlButtons.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.ui.Modifier -import com.tangem.core.ui.components.buttons.HorizontalActionChips -import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList - -private const val CONTROL_BUTTONS_CONTENT_TYPE = "ControlButtons" - -/** - * Single currency control buttons. Like, "Buy", "Sell", etc - * - * @param configs list of buttons - * @param selectedWalletIndex selected wallet index for creating a unique key - * @param modifier modifier - * -[REDACTED_AUTHOR] - */ -@OptIn(ExperimentalFoundationApi::class) -internal fun LazyListScope.controlButtons( - configs: ImmutableList, - selectedWalletIndex: Int, - modifier: Modifier = Modifier, -) { - item(key = CONTROL_BUTTONS_CONTENT_TYPE + selectedWalletIndex, contentType = CONTROL_BUTTONS_CONTENT_TYPE) { - HorizontalActionChips( - buttons = configs.map(WalletManageButton::config).toImmutableList(), - modifier = modifier.animateItemPlacement(), - contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index fc787ace14..a9af33b972 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -31,6 +31,7 @@ import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvid import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder @@ -69,6 +70,7 @@ internal class WalletViewModel @Inject constructor( private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, private val walletImageResolver: WalletImageResolver, private val tokenListStore: MultiWalletTokenListStore, + private val walletFeatureToggles: WalletFeatureToggles, analyticsEventsHandler: AnalyticsEventHandler, ) : ViewModel() { @@ -300,6 +302,7 @@ internal class WalletViewModel @Inject constructor( wallets = action.wallets, clickIntents = clickIntents, walletImageResolver = walletImageResolver, + walletFeatureToggles = walletFeatureToggles, ), ) @@ -334,6 +337,7 @@ internal class WalletViewModel @Inject constructor( newUserWallet = action.selectedWallet, clickIntents = clickIntents, walletImageResolver = walletImageResolver, + walletFeatureToggles = walletFeatureToggles, ), ) } @@ -350,6 +354,7 @@ internal class WalletViewModel @Inject constructor( userWallet = action.selectedWallet, clickIntents = clickIntents, walletImageResolver = walletImageResolver, + walletFeatureToggles = walletFeatureToggles, ), ) @@ -401,6 +406,7 @@ internal class WalletViewModel @Inject constructor( unlockedWallets = action.unlockedWallets, clickIntents = clickIntents, walletImageResolver = walletImageResolver, + walletFeatureToggles = walletFeatureToggles, ), ) From 342b6a2f501d2d7a460c782171e6df0c071a4418 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Nov 2024 12:53:30 +0500 Subject: [PATCH 16/33] Updated on 2026-08-14 --- .../tap/di/domain/OnrampDomainModule.kt | 21 ++++++- .../local/preferences/PreferencesKeys.kt | 2 + .../data/onramp/DefaultOnrampRepository.kt | 34 +++++++++-- .../onramp/converters/CountryConverter.kt | 19 +++++-- .../domain/onramp/model/OnrampCountry.kt | 1 + .../onramp/GetOnrampCountriesUseCase.kt | 12 ++++ .../domain/onramp/GetOnrampCountryUseCase.kt | 23 ++++++++ .../onramp/OnrampSaveDefaultCountryUseCase.kt | 11 ++++ .../OnrampSaveDefaultCurrencyUseCase.kt | 4 +- .../onramp/repositories/OnrampRepository.kt | 4 ++ .../DefaultSelectCountryComponent.kt | 4 +- .../selectcountry/entity/CountryListUM.kt | 2 +- .../entity/CountryListUMController.kt | 10 +++- .../UpdateCountryItemsTransformer.kt | 42 +++++++++++--- .../model/MockedCountriesData.kt | 3 - .../model/OnrampSelectCountryModel.kt | 51 +++++++++++------ .../selectcountry/ui/CountryListItem.kt | 4 +- .../DefaultOnrampSettingsComponent.kt | 4 +- .../settings/entity/OnrampSettingsItemUM.kt | 6 +- .../settings/model/OnrampSettingsModel.kt | 57 +++++++++++++++---- 20 files changed, 254 insertions(+), 60 deletions(-) create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountriesUseCase.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountryUseCase.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCountryUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt index b485886c06..29cd227fdd 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -1,7 +1,6 @@ package com.tangem.tap.di.domain -import com.tangem.domain.onramp.GetOnrampCurrenciesUseCase -import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase +import com.tangem.domain.onramp.* import com.tangem.domain.onramp.repositories.OnrampRepository import dagger.Module import dagger.Provides @@ -24,4 +23,22 @@ internal object OnrampDomainModule { fun provideOnrampSaveDefaultCurrencyUseCase(onrampRepository: OnrampRepository): OnrampSaveDefaultCurrencyUseCase { return OnrampSaveDefaultCurrencyUseCase(onrampRepository) } + + @Provides + @Singleton + fun provideGetOnrampCountriesUseCase(onrampRepository: OnrampRepository): GetOnrampCountriesUseCase { + return GetOnrampCountriesUseCase(onrampRepository) + } + + @Provides + @Singleton + fun provideGetOnrampCountryUseCase(onrampRepository: OnrampRepository): GetOnrampCountryUseCase { + return GetOnrampCountryUseCase(onrampRepository) + } + + @Provides + @Singleton + fun provideOnrampSaveDefaultCountryUseCase(onrampRepository: OnrampRepository): OnrampSaveDefaultCountryUseCase { + return OnrampSaveDefaultCountryUseCase(onrampRepository) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 60946f6ff4..a049a7db22 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -105,6 +105,8 @@ object PreferencesKeys { val ONRAMP_DEFAULT_CURRENCY by lazy { stringPreferencesKey(name = "onrampDefaultCurrency") } + val ONRAMP_DEFAULT_COUNTRY by lazy { stringPreferencesKey(name = "onrampDefaultCountry") } + // region Permission fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission") diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 55fcfc1077..31afbf4c67 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -4,6 +4,7 @@ import com.tangem.data.onramp.converters.CountryConverter import com.tangem.data.onramp.converters.CurrencyConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.onramp.OnrampApi +import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys @@ -25,7 +26,7 @@ internal class DefaultOnrampRepository( ) : OnrampRepository { private val currencyConverter = CurrencyConverter() - private val countryConverter = CountryConverter() + private val countryConverter = CountryConverter(currencyConverter) override suspend fun getCurrencies(): List = withContext(dispatchers.io) { onrampApi.getCurrencies() @@ -39,15 +40,21 @@ internal class DefaultOnrampRepository( .map(countryConverter::convert) } - override suspend fun saveDefaultCurrency(currency: OnrampCurrency) { + override suspend fun getCountryByIp(): OnrampCountry = withContext(dispatchers.io) { + onrampApi.getCountryByIp() + .getOrThrow() + .let(countryConverter::convert) + } + + override suspend fun saveDefaultCurrency(currency: OnrampCurrency) = withContext(dispatchers.io) { appPreferencesStore.storeObject( key = PreferencesKeys.ONRAMP_DEFAULT_CURRENCY, value = currencyConverter.convertBack(currency), ) } - override suspend fun getDefaultCurrencySync(): OnrampCurrency? { - return appPreferencesStore + override suspend fun getDefaultCurrencySync(): OnrampCurrency? = withContext(dispatchers.io) { + appPreferencesStore .getObjectSyncOrNull(PreferencesKeys.ONRAMP_DEFAULT_CURRENCY) ?.let(currencyConverter::convert) } @@ -57,4 +64,23 @@ internal class DefaultOnrampRepository( .getObject(PreferencesKeys.ONRAMP_DEFAULT_CURRENCY) .map { it?.let(currencyConverter::convert) } } + + override suspend fun saveDefaultCountry(country: OnrampCountry) = withContext(dispatchers.io) { + appPreferencesStore.storeObject( + key = PreferencesKeys.ONRAMP_DEFAULT_COUNTRY, + value = countryConverter.convertBack(country), + ) + } + + override suspend fun getDefaultCountrySync(): OnrampCountry? = withContext(dispatchers.io) { + appPreferencesStore + .getObjectSyncOrNull(PreferencesKeys.ONRAMP_DEFAULT_COUNTRY) + ?.let(countryConverter::convert) + } + + override fun getDefaultCountry(): Flow { + return appPreferencesStore + .getObject(PreferencesKeys.ONRAMP_DEFAULT_COUNTRY) + .map { it?.let(countryConverter::convert) } + } } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/CountryConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/CountryConverter.kt index 6d1c21f2d8..97099c62b1 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/converters/CountryConverter.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/CountryConverter.kt @@ -2,13 +2,14 @@ package com.tangem.data.onramp.converters import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO import com.tangem.domain.onramp.model.OnrampCountry -import com.tangem.utils.converter.Converter +import com.tangem.utils.converter.TwoWayConverter -internal class CountryConverter : Converter { - - private val currencyConverter = CurrencyConverter() +internal class CountryConverter( + private val currencyConverter: CurrencyConverter, +) : TwoWayConverter { override fun convert(value: OnrampCountryDTO) = OnrampCountry( + id = "${value.alpha3}-${value.name}", name = value.name, code = value.code, image = value.image, @@ -17,4 +18,14 @@ internal class CountryConverter : Converter { defaultCurrency = currencyConverter.convert(value.defaultCurrency), onrampAvailable = value.onrampAvailable, ) + + override fun convertBack(value: OnrampCountry): OnrampCountryDTO = OnrampCountryDTO( + name = value.name, + code = value.code, + image = value.image, + alpha3 = value.alpha3, + continent = value.continent, + defaultCurrency = currencyConverter.convertBack(value.defaultCurrency), + onrampAvailable = value.onrampAvailable, + ) } \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt index fb718b7cc1..1a3a9b69dc 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt @@ -1,6 +1,7 @@ package com.tangem.domain.onramp.model data class OnrampCountry( + val id: String, val name: String, val code: String, val image: String, diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountriesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountriesUseCase.kt new file mode 100644 index 0000000000..bbf1323710 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountriesUseCase.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampCountry +import com.tangem.domain.onramp.repositories.OnrampRepository + +class GetOnrampCountriesUseCase(private val onrampRepository: OnrampRepository) { + + suspend operator fun invoke(): Either> { + return Either.catch { onrampRepository.getCountries() } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountryUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountryUseCase.kt new file mode 100644 index 0000000000..209324027a --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountryUseCase.kt @@ -0,0 +1,23 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.domain.onramp.model.OnrampCountry +import com.tangem.domain.onramp.repositories.OnrampRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map + +class GetOnrampCountryUseCase(private val repository: OnrampRepository) { + + operator fun invoke(): Flow> { + return repository.getDefaultCountry() + .map> { it.right() } + .catch { emit(it.left()) } + } + + suspend fun invokeSync(): Either { + return Either.catch { repository.getDefaultCountrySync() ?: repository.getCountryByIp() } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCountryUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCountryUseCase.kt new file mode 100644 index 0000000000..e939cfee5f --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCountryUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.onramp + +import com.tangem.domain.onramp.model.OnrampCountry +import com.tangem.domain.onramp.repositories.OnrampRepository + +class OnrampSaveDefaultCountryUseCase(private val repository: OnrampRepository) { + + suspend operator fun invoke(country: OnrampCountry) { + repository.saveDefaultCountry(country) + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt index e30213f5ab..6362609fbf 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt @@ -3,9 +3,9 @@ package com.tangem.domain.onramp import com.tangem.domain.onramp.model.OnrampCurrency import com.tangem.domain.onramp.repositories.OnrampRepository -class OnrampSaveDefaultCurrencyUseCase(private val onrampRepository: OnrampRepository) { +class OnrampSaveDefaultCurrencyUseCase(private val repository: OnrampRepository) { suspend operator fun invoke(currency: OnrampCurrency) { - onrampRepository.saveDefaultCurrency(currency) + repository.saveDefaultCurrency(currency) } } \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt index 73c41ce3db..85b4e27f69 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt @@ -7,7 +7,11 @@ import kotlinx.coroutines.flow.Flow interface OnrampRepository { suspend fun getCurrencies(): List suspend fun getCountries(): List + suspend fun getCountryByIp(): OnrampCountry suspend fun saveDefaultCurrency(currency: OnrampCurrency) suspend fun getDefaultCurrencySync(): OnrampCurrency? fun getDefaultCurrency(): Flow + suspend fun saveDefaultCountry(country: OnrampCountry) + suspend fun getDefaultCountrySync(): OnrampCountry? + fun getDefaultCountry(): Flow } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt index 60c7d65ff5..35fc452dc8 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt @@ -19,13 +19,13 @@ import dagger.assisted.AssistedInject internal class DefaultSelectCountryComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, - @Assisted private val params: SelectCountryComponent.Params, + @Assisted params: SelectCountryComponent.Params, ) : SelectCountryComponent, AppComponentContext by appComponentContext { private val model: OnrampSelectCountryModel = getOrCreateModel(params) override fun dismiss() { - params.onDismiss() + model.dismiss() } @Composable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt index f1e6365c95..6006c973c8 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt @@ -11,7 +11,7 @@ internal data class CountryListUM(val items: ImmutableList) } /** Get tokens */ - fun getTokens(): ImmutableList { + fun getCountries(): ImmutableList { if (getSearchBar() == null) return items return if (items.size > 1) { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt index 5956d4128d..88015101bb 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt @@ -1,7 +1,6 @@ package com.tangem.features.onramp.selectcountry.entity import com.tangem.features.onramp.selectcountry.entity.transformer.UpdateCountryItemsTransformer -import com.tangem.features.onramp.selectcountry.model.MockedCountriesData import com.tangem.features.onramp.utils.SearchBarUMTransformer import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -11,12 +10,14 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import javax.inject.Inject +private const val LOADING_ITEMS_COUNT = 5 + internal class CountryListUMController @Inject constructor() { val state: StateFlow get() = _state.asStateFlow() private val _state: MutableStateFlow = MutableStateFlow( value = CountryListUM( - items = MockedCountriesData.getLoadingItems().map(CountriesListItemUM::Country).toImmutableList(), + items = getLoadingItems().map(CountriesListItemUM::Country).toImmutableList(), ), ) @@ -35,7 +36,7 @@ internal class CountryListUMController @Inject constructor() { prevState.copy( items = persistentListOf( updatedSearchBar, - *prevState.getTokens().toTypedArray(), + *prevState.getCountries().toTypedArray(), ), ) } else { @@ -48,4 +49,7 @@ internal class CountryListUMController @Inject constructor() { fun getSearchBar(): CountriesListItemUM.SearchBar? { return _state.value.getSearchBar() } + + private fun getLoadingItems(): List = + MutableList(LOADING_ITEMS_COUNT) { CountryItemState.Loading("Loading #$it") } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt index 9b8e8b9f73..5489a9910c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt @@ -1,7 +1,9 @@ package com.tangem.features.onramp.selectcountry.entity.transformer +import arrow.core.Either import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selectcountry.entity.CountriesListItemUM import com.tangem.features.onramp.selectcountry.entity.CountryItemState @@ -10,22 +12,48 @@ import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.toImmutableList internal class UpdateCountryItemsTransformer( - private val countries: List, + private val maybeCountries: Either>, + private val defaultCountry: OnrampCountry?, + private val query: String, private val onQueryChange: (String) -> Unit, private val onActiveChange: (Boolean) -> Unit, + private val onCountryClick: (OnrampCountry) -> Unit, ) : Transformer { override fun transform(prevState: CountryListUM): CountryListUM { val searchBarItem = prevState.getSearchBar() ?: createSearchBarItem() - - return prevState.copy( - items = (listOfNotNull(searchBarItem) + countries.map(::convertCountry)).toImmutableList(), + return maybeCountries.fold( + ifLeft = { prevState }, // TODO: [REDACTED_JIRA] + ifRight = { countries -> + val countriesListItems = countries.filterByQuery().toUiModels() + prevState.copy(items = (listOf(searchBarItem) + countriesListItems).toImmutableList()) + }, ) } - // TODO: Temporarily. Will be refactored after implement domain - private fun convertCountry(state: CountryItemState): CountriesListItemUM.Country = - CountriesListItemUM.Country(state) + private fun List.toUiModels(): List { + return map { country -> + val countryItemState = if (country.onrampAvailable) { + CountryItemState.Content( + id = country.id, + flagUrl = country.image, + countryName = country.name, + isSelected = defaultCountry?.id?.equals(country.id) == true, + onClick = { onCountryClick(country) }, + ) + } else { + CountryItemState.Unavailable(id = country.id, flagUrl = country.image, countryName = country.name) + } + CountriesListItemUM.Country(countryItemState) + } + } + + private fun List.filterByQuery(): List { + return filter { country -> + country.alpha3.lowercase().contains(query.lowercase()) || + country.name.lowercase().contains(query.lowercase()) + } + } private fun createSearchBarItem(): CountriesListItemUM.SearchBar { return CountriesListItemUM.SearchBar( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt index 0c9622c58a..59c6d5d7c0 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt @@ -13,9 +13,6 @@ internal object MockedCountriesData { delay(3_000) // simulate network call emit(getMockedCountries()) } - fun getLoadingItems(): List = MutableList(5) { - CountryItemState.Loading("Loading #$it") - } private fun getMockedCountries(): List = MutableList(50) { index -> if (index % 5 == 0) { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt index 4dd57e776f..8c4915a779 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt @@ -2,9 +2,14 @@ package com.tangem.features.onramp.selectcountry.model import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.onramp.GetOnrampCountriesUseCase +import com.tangem.domain.onramp.GetOnrampCountryUseCase +import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase +import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.selectcountry.entity.CountryItemState +import com.tangem.features.onramp.selectcountry.SelectCountryComponent import com.tangem.features.onramp.selectcountry.entity.CountryListUM import com.tangem.features.onramp.selectcountry.entity.CountryListUMController import com.tangem.features.onramp.selectcountry.entity.transformer.UpdateCountryItemsTransformer @@ -17,36 +22,54 @@ import kotlinx.coroutines.launch import javax.inject.Inject @ComponentScoped +@Suppress("LongParameterList") internal class OnrampSelectCountryModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val searchManager: SearchManager, private val countryListUMController: CountryListUMController, + private val getOnrampCountriesUseCase: GetOnrampCountriesUseCase, + private val saveDefaultCountryUseCase: OnrampSaveDefaultCountryUseCase, + private val getOnrampCountryUseCase: GetOnrampCountryUseCase, + paramsContainer: ParamsContainer, ) : Model() { val state: StateFlow = countryListUMController.state + private val params: SelectCountryComponent.Params = paramsContainer.require() init { - subscribeOnUpdateState() + modelScope.launch { subscribeOnUpdateState() } } - private fun subscribeOnUpdateState() { - combine( - flow = MockedCountriesData.getCountryItems(), - flow2 = searchManager.query, - ) { countryItems, query -> - val filteredCountryItems = countryItems.filterByQuery(query = query) + fun dismiss() { + params.onDismiss() + } + private suspend fun subscribeOnUpdateState() { + combine( + flow = flowOf(getOnrampCountriesUseCase.invoke()), + flow2 = getOnrampCountryUseCase.invoke(), + flow3 = searchManager.query, + ) { maybeCountries, maybeCountry, query -> UpdateCountryItemsTransformer( - countries = filteredCountryItems, + maybeCountries = maybeCountries, + defaultCountry = maybeCountry.getOrNull(), + query = query, onQueryChange = ::onSearchQueryChange, onActiveChange = ::onSearchBarActiveChange, + onCountryClick = ::saveCountry, ) } .onEach(countryListUMController::update) - .flowOn(dispatchers.main) .launchIn(modelScope) } + private fun saveCountry(country: OnrampCountry) { + modelScope.launch { + saveDefaultCountryUseCase.invoke(country) + dismiss() + } + } + private fun onSearchQueryChange(newQuery: String) { val searchBar = countryListUMController.getSearchBar() if (searchBar?.searchBarUM?.query == newQuery) return @@ -66,12 +89,4 @@ internal class OnrampSelectCountryModel @Inject constructor( ), ) } - - // TODO: Temporarily. Will be refactored after implement domain - private fun List.filterByQuery(query: String): List { - return filter { - (it as? CountryItemState.Content)?.countryName?.contains(query) == true || - (it as? CountryItemState.Unavailable)?.countryName?.contains(query) == true - } - } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt index 1b285b7b66..7bc00833a0 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt @@ -29,7 +29,9 @@ internal fun CountryListItem(state: CountriesListItemUM, modifier: Modifier = Mo when (state) { is CountriesListItemUM.Country -> CountryItem( state = state.state, - modifier = modifier.countryClickable(state.state).padding(all = TangemTheme.dimens.spacing16), + modifier = modifier + .countryClickable(state.state) + .padding(all = TangemTheme.dimens.spacing16), ) is CountriesListItemUM.SearchBar -> SearchBar( state = state.searchBarUM, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt index 2fff47ec00..efec6676c4 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/DefaultOnrampSettingsComponent.kt @@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot @@ -36,8 +37,9 @@ internal class DefaultOnrampSettingsComponent @AssistedInject constructor( @Composable override fun Content(modifier: Modifier) { + val state by model.state.collectAsStateWithLifecycle() BackHandler(onBack = params.onBack) - OnrampSettingsContent(modifier = modifier, state = model.state) + OnrampSettingsContent(modifier = modifier, state = state) val bottomSheet by bottomSheetSlot.subscribeAsState() bottomSheet.child?.instance?.BottomSheet() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsItemUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsItemUM.kt index f957739a26..9bd7ad60a1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsItemUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/entity/OnrampSettingsItemUM.kt @@ -1,5 +1,9 @@ package com.tangem.features.onramp.settings.entity sealed class OnrampSettingsItemUM { - data class Residence(val countryName: String, val flagUrl: String, val onClick: () -> Unit) : OnrampSettingsItemUM() + data class Residence( + val countryName: String = "", + val flagUrl: String = "", + val onClick: () -> Unit, + ) : OnrampSettingsItemUM() } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt index edff4909ee..0f41e6f6ca 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt @@ -1,34 +1,69 @@ package com.tangem.features.onramp.settings.model +import arrow.core.Either import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.onramp.GetOnrampCountryUseCase +import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.features.onramp.settings.OnrampSettingsComponent import com.tangem.features.onramp.settings.entity.OnrampSettingsConfig import com.tangem.features.onramp.settings.entity.OnrampSettingsItemUM import com.tangem.features.onramp.settings.entity.OnrampSettingsUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.* import javax.inject.Inject @ComponentScoped internal class OnrampSettingsModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, + private val getOnrampCountryUseCase: GetOnrampCountryUseCase, paramsContainer: ParamsContainer, ) : Model() { - private val params: OnrampSettingsComponent.Params = paramsContainer.require() + val state: StateFlow get() = _state val bottomSheetNavigation: SlotNavigation = SlotNavigation() - val state = OnrampSettingsUM( - onBack = params.onBack, - items = listOf( - OnrampSettingsItemUM.Residence( - countryName = "United States of America", - flagUrl = "", - onClick = { bottomSheetNavigation.activate(OnrampSettingsConfig.SelectCountry) }, - ), - ).toImmutableList(), - ) + + private val params: OnrampSettingsComponent.Params = paramsContainer.require() + private val _state: MutableStateFlow = MutableStateFlow(getInitialState()) + + init { + subscribeOnUpdateState() + } + + private fun subscribeOnUpdateState() { + getOnrampCountryUseCase.invoke() + .onEach(::updateResidenceState) + .launchIn(modelScope) + } + + private fun updateResidenceState(maybeCountry: Either) { + maybeCountry.onRight { country -> + _state.update { state -> + state.copy( + items = listOf( + OnrampSettingsItemUM.Residence( + countryName = country?.name.orEmpty(), + flagUrl = country?.image.orEmpty(), + onClick = ::openSelectCountryBottomSheet, + ), + ).toImmutableList(), + ) + } + } + } + + private fun openSelectCountryBottomSheet() { + bottomSheetNavigation.activate(OnrampSettingsConfig.SelectCountry) + } + + private fun getInitialState(): OnrampSettingsUM { + return OnrampSettingsUM( + onBack = params.onBack, + items = listOf(OnrampSettingsItemUM.Residence(onClick = ::openSelectCountryBottomSheet)).toImmutableList(), + ) + } } \ No newline at end of file From ec40334d8ae0311e5cfe851f34ee21144d67e21c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Nov 2024 11:56:45 +0300 Subject: [PATCH 17/33] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 18 +- .../java/com/tangem/tap/di/TangemSdkModule.kt | 17 ++ .../features/onboarding/OnboardingHelper.kt | 5 +- .../tangem/tap/routing/utils/ChildFactory.kt | 5 +- .../com/tangem/common/routing/AppRoute.kt | 5 +- .../v2/entry/OnboardingEntryComponent.kt | 1 + features/onboarding-v2/impl/build.gradle.kts | 2 + .../entry/impl/model/OnboardingEntryModel.kt | 4 +- .../impl/routing/OnboardingChildFactory.kt | 4 +- .../v2/entry/impl/routing/OnboardingRoute.kt | 2 +- .../v2/entry/impl/ui/OnboardingEntry.kt | 2 +- .../DefaultOnboardingMultiWalletComponent.kt | 9 +- .../backup/MultiWalletBackupComponent.kt | 5 + .../impl/child/backup/model/BackupState.kt | 48 +++++ .../backup/model/MultiWalletBackupModel.kt | 180 +++++++++++++++++- .../impl/child/backup/ui/Dialogs.kt | 27 +++ .../impl/child/backup/ui/MultiWalletBackup.kt | 112 ++++++++++- .../impl/child/backup/ui/Wallet1Artwork.kt | 21 ++ .../backup/ui/state/MultiWalletBackupUM.kt | 46 ++++- .../impl/child/createwallet/model/Dialogs.kt | 19 +- .../model/MultiWalletCreateWalletModel.kt | 14 +- .../ui/MultiWalletCreateWallet.kt | 1 + .../ui/state/MultiWalletCreateWalletUM.kt | 1 + .../impl/model/OnboardingMultiWalletModel.kt | 11 +- .../impl/model/OnboardingMultiWalletState.kt | 1 - .../com/tangem/sdk/api/BackupServiceHolder.kt | 17 ++ 26 files changed, 527 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/di/TangemSdkModule.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/BackupState.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Dialogs.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt create mode 100644 libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/BackupServiceHolder.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index cc4284cc7a..e0669f7e68 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -52,6 +52,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent +import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.send.api.navigation.SendRouter @@ -59,6 +60,7 @@ import com.tangem.features.staking.api.navigation.StakingRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.operations.backup.BackupService +import com.tangem.sdk.api.BackupServiceHolder import com.tangem.sdk.api.TangemSdkManager import com.tangem.sdk.extensions.init import com.tangem.tap.common.ActivityResultCallbackHolder @@ -185,6 +187,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase + @Inject + lateinit var backupServiceHolder: BackupServiceHolder + + @Inject + lateinit var onboardingV2FeatureToggles: OnboardingV2FeatureToggles + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -294,7 +302,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac cardSdkOwner.register(activity = this) tangemSdkManager = injectedTangemSdkManager appStateHolder.tangemSdkManager = tangemSdkManager - backupService = BackupService.init(cardSdkConfigRepository.sdk, this) + + if (onboardingV2FeatureToggles.isOnboardingV2Enabled) { + backupServiceHolder.createAndSetService(cardSdkConfigRepository.sdk, this) + backupService = backupServiceHolder.backupService.get()!! // will be deleted eventually + } else { + backupService = BackupService.init(cardSdkConfigRepository.sdk, this) + } + lockUserWalletsTimer = LockUserWalletsTimer( owner = this, settingsRepository = settingsRepository, @@ -550,6 +565,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } } + // TODO add onboarding v2 handling store.dispatch(BackupAction.CheckForUnfinishedBackup) } diff --git a/app/src/main/java/com/tangem/tap/di/TangemSdkModule.kt b/app/src/main/java/com/tangem/tap/di/TangemSdkModule.kt new file mode 100644 index 0000000000..b2f2f6d5c5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/TangemSdkModule.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.di + +import com.tangem.sdk.api.BackupServiceHolder +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object TangemSdkModule { + + @Provides + @Singleton + fun provideBackupServiceHolder(): BackupServiceHolder = BackupServiceHolder() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 2f18b059ca..7ee5761836 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -67,7 +67,10 @@ object OnboardingHelper { fun whereToNavigate(scanResponse: ScanResponse): AppRoute { if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled) { - return AppRoute.Onboarding(scanResponse) + return AppRoute.Onboarding( + scanResponse = scanResponse, + startFromBackup = false, + ) } return when (val type = scanResponse.productType) { diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index b031d03e46..fe71e3ac0d 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -218,7 +218,10 @@ internal class ChildFactory @Inject constructor( is AppRoute.Onboarding -> { route.asComponentChild( contextProvider = contextProvider(route, contextFactory), - params = OnboardingEntryComponent.Params(route.scanResponse), + params = OnboardingEntryComponent.Params( + scanResponse = route.scanResponse, + startBackupFlow = route.startFromBackup, + ), componentFactory = onboardingEntryComponentFactory, ) } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 6744b6c0be..f5982495e5 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -300,5 +300,8 @@ sealed class AppRoute(val path: String) : Route { ) : AppRoute(path = "/sell_crypto/${userWalletId.stringValue}") // Onboarding V2 - data class Onboarding(val scanResponse: ScanResponse) : AppRoute(path = "/onboarding_v2") + data class Onboarding( + val scanResponse: ScanResponse, + val startFromBackup: Boolean, + ) : AppRoute(path = "/onboarding_v2${if (startFromBackup) "/backup" else ""}") } \ No newline at end of file diff --git a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt index 847e64541e..3dafb568ad 100644 --- a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt +++ b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt @@ -8,6 +8,7 @@ interface OnboardingEntryComponent : ComposableContentComponent { data class Params( val scanResponse: ScanResponse, + val startBackupFlow: Boolean, ) interface Factory : ComponentFactory diff --git a/features/onboarding-v2/impl/build.gradle.kts b/features/onboarding-v2/impl/build.gradle.kts index 25f02a083b..ba4fb5017f 100644 --- a/features/onboarding-v2/impl/build.gradle.kts +++ b/features/onboarding-v2/impl/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation(projects.domain.models) implementation(projects.domain.feedback) implementation(projects.domain.core) + implementation(projects.domain.card) /** Tangem libraries */ implementation(projects.libs.tangemSdkApi) @@ -54,6 +55,7 @@ dependencies { implementation(deps.kotlin.immutable.collections) implementation(deps.kotlin.serialization) implementation(deps.timber) + implementation(deps.firebase.crashlytics) /** DI */ implementation(deps.hilt.android) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt index 9a040ff17c..07053a7b9e 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt @@ -40,12 +40,12 @@ internal class OnboardingEntryModel @Inject constructor( return when (scanResponse.productType) { ProductType.Note -> TODO() ProductType.Twins -> TODO() - ProductType.Wallet -> OnboardingRoute.Wallet12( + ProductType.Wallet -> OnboardingRoute.MultiWallet( scanResponse = scanResponse, withSeedPhraseFlow = false, titleProvider = titleProvider, ) - ProductType.Wallet2 -> OnboardingRoute.Wallet12( + ProductType.Wallet2 -> OnboardingRoute.MultiWallet( scanResponse = scanResponse, withSeedPhraseFlow = true, titleProvider = titleProvider, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt index 3cd1b811a8..e25190780d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt @@ -5,12 +5,12 @@ import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletCo import javax.inject.Inject internal class OnboardingChildFactory @Inject constructor( - private val wallet12ComponentFactory: OnboardingMultiWalletComponent.Factory, + private val multiWalletComponentFactory: OnboardingMultiWalletComponent.Factory, ) { fun createChild(route: OnboardingRoute, childContext: AppComponentContext): Any { return when (route) { - is OnboardingRoute.Wallet12 -> wallet12ComponentFactory.create( + is OnboardingRoute.MultiWallet -> multiWalletComponentFactory.create( context = childContext, params = OnboardingMultiWalletComponent.Params( scanResponse = route.scanResponse, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt index 4dbd6593f0..b315398e7c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt @@ -8,7 +8,7 @@ sealed class OnboardingRoute : Route { data object None : OnboardingRoute() - data class Wallet12( + data class MultiWallet( val titleProvider: TitleProvider, val scanResponse: ScanResponse, val withSeedPhraseFlow: Boolean, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt index 16400dd642..37d5e6acd7 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt @@ -31,7 +31,7 @@ internal inline fun OnboardingEntry( animation = stackAnimation(slide()), ) { when (it.configuration) { - is OnboardingRoute.Wallet12 -> { + is OnboardingRoute.MultiWallet -> { (it.instance as OnboardingMultiWalletComponent).Content( modifier = modifier, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt index 8fbb204794..9b8676533f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt @@ -32,12 +32,11 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor ) : OnboardingMultiWalletComponent, AppComponentContext by context { private val model: OnboardingMultiWalletModel = getOrCreateModel(params) - private val stackNavigation = StackNavigation() override val innerNavigation: InnerNavigation = object : InnerNavigation { override val state = MutableStateFlow( - Wallet12InnerNavigationState(1, 5), + MultiWalletInnerNavigationState(1, 5), ) override fun pop(onComplete: (Boolean) -> Unit) { @@ -83,8 +82,10 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor private fun onStepDone() { when (model.state.value.currentStep) { - OnboardingMultiWalletState.Step.CreateWallet -> + OnboardingMultiWalletState.Step.CreateWallet -> { stackNavigation.push(OnboardingMultiWalletState.Step.AddBackupDevice) + // innerNavigation.state.value TODO + } OnboardingMultiWalletState.Step.AddBackupDevice -> TODO() OnboardingMultiWalletState.Step.FinishBackup -> TODO() OnboardingMultiWalletState.Step.Done -> TODO() @@ -112,7 +113,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor } } -data class Wallet12InnerNavigationState( +data class MultiWalletInnerNavigationState( override val stackSize: Int, override val stackMaxSize: Int?, ) : InnerNavigationState \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt index ff0422eeab..f68f4741b3 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt @@ -12,6 +12,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.MultiWalletBackupModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.MultiWalletBackup +import kotlinx.coroutines.launch @Suppress("UnusedPrivateMember") class MultiWalletBackupComponent( @@ -26,6 +27,10 @@ class MultiWalletBackupComponent( params.parentParams.titleProvider.changeTitle( text = resourceReference(R.string.onboarding_navbar_title_creating_backup), ) + + componentScope.launch { + model.onDoneFlow.collect { onDone() } + } } @Composable diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/BackupState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/BackupState.kt new file mode 100644 index 0000000000..b1fd1a646c --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/BackupState.kt @@ -0,0 +1,48 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model + +import android.graphics.Bitmap +import com.tangem.common.card.Card + +// TODO maybe delete some fields in the future +data class BackupState( + val primaryCardId: String? = null, + val primaryCardBatchId: String? = null, + val backupCardsNumber: Int = 0, + val backupCards: List = emptyList(), + val backupCardIds: List = emptyList(), + val backupCardBatchIds: List = emptyList(), + @Transient + val backupCardsArtworks: Map = emptyMap(), + val accessCode: String = "", + val accessCodeError: AccessCodeError? = null, + val backupStep: BackupStep? = null, + val maxBackupCards: Int = 2, + val canSkipBackup: Boolean = true, + val isInterruptedBackup: Boolean = false, + val showBtnLoading: Boolean = false, + val hasBackupError: Boolean = false, + val startedSource: BackupStartedSource = BackupStartedSource.Onboarding, + val hasRing: Boolean = false, +) + +enum class BackupStartedSource { + Onboarding, CreateBackup +} + +enum class AccessCodeError { + CodeTooShort, CodesDoNotMatch +} + +typealias CardId = String + +sealed class BackupStep { + data object InitBackup : BackupStep() + data object ScanOriginCard : BackupStep() + data object AddBackupCards : BackupStep() + data object SetAccessCode : BackupStep() + data object EnterAccessCode : BackupStep() + data object ReenterAccessCode : BackupStep() + data object WritePrimaryCard : BackupStep() + data class WriteBackupCard(val cardNumber: Int) : BackupStep() + data object Finished : BackupStep() +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt index 1b6ddac97b..66ffa8ff7f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt @@ -1,31 +1,201 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.models.scan.ProductType +import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams +import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.backupCardAttestationFailedDialog +import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.resetBackupCardDialog import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUM +import com.tangem.operations.backup.BackupService +import com.tangem.sdk.api.BackupServiceHolder +import com.tangem.sdk.api.TangemSdkManager import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import javax.inject.Inject @ComponentScoped class MultiWalletBackupModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, + private val backupServiceHolder: BackupServiceHolder, + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val tangemSdkManager: TangemSdkManager, ) : Model() { @Suppress("UnusedPrivateMember") private val params = paramsContainer.require() + private val scanResponse + get() = params.multiWalletState.value.currentScanResponse + private val backupService + get() = backupServiceHolder.backupService.get()!! + + private val state = MutableStateFlow(BackupState()) private val _uiState = MutableStateFlow( - MultiWalletBackupUM( - finalizeButtonEnabled = false, - onAddBackupClick = {}, - onFinalizeButtonClick = {}, - ), + when (scanResponse.productType) { + ProductType.Wallet -> getWallet1State() + ProductType.Wallet2, + ProductType.Ring, + -> MultiWalletBackupUM.Wallet2( + isRing = scanResponse.productType == ProductType.Ring, + backupAdded = false, // backupService.hasIncompletedBackup, + onAddBackupClick = {}, + onFinalizeButtonClick = {}, + ) + else -> error("Type: ${scanResponse.productType.name} is not supported!") + }, ) val uiState: StateFlow = _uiState + val onDoneFlow = MutableSharedFlow() + + private fun getWallet1State(): MultiWalletBackupUM.Wallet1 { + return when (backupService.currentState) { + BackupService.State.Preparing -> { + MultiWalletBackupUM.Wallet1( + title = resourceReference(R.string.onboarding_title_no_backup_cards), + bodyText = resourceReference(R.string.onboarding_subtitle_no_backup_cards), + showFinalizeButton = false, + addBackupButtonEnabled = true, + addBackupButtonLoading = false, + artworkNumberOfBackupCards = 0, + onAddBackupClick = ::startBackupWallet1, + onFinalizeButtonClick = {}, + onSkipButtonClick = { onDoneFlow.tryEmit(Unit) }, + ) + } + is BackupService.State.FinalizingBackupCard -> TODO() + BackupService.State.FinalizingPrimaryCard -> TODO() + BackupService.State.Finished -> TODO() + } + } + + private fun startBackupWallet1() { + backupService.discardSavedBackup() + val primaryCard = scanResponse.primaryCard + + if (primaryCard != null) { + backupService.setPrimaryCard(primaryCard) + addBackupCardWithService() + } else { + // TODO wallet 2 ??? or from main? + // BackupAction.StartAddingPrimaryCard + } + } + + private fun setNumberOfBackupCardsWallet1(number: Int) { + // set state for Wallet1 for adding backup cards and disable button if there is more than 2 backup cards + _uiState.update { st -> + if (st !is MultiWalletBackupUM.Wallet1) return@update st + + when (number) { + 0 -> { + st.copy( + title = resourceReference(R.string.onboarding_title_no_backup_cards), + bodyText = resourceReference(R.string.onboarding_subtitle_no_backup_cards), + artworkNumberOfBackupCards = 0, + addBackupButtonEnabled = true, + ) + } + 1 -> { + st.copy( + title = resourceReference(R.string.onboarding_title_one_backup_card), + bodyText = resourceReference(R.string.onboarding_subtitle_one_backup_card), + artworkNumberOfBackupCards = 1, + addBackupButtonEnabled = true, + showFinalizeButton = true, + ) + } + 2 -> { + st.copy( + title = resourceReference(R.string.onboarding_title_two_backup_cards), + bodyText = resourceReference(R.string.onboarding_subtitle_two_backup_cards), + artworkNumberOfBackupCards = 2, + addBackupButtonEnabled = false, + ) + } + else -> st.copy(addBackupButtonEnabled = false) + } + } + } + + private fun addBackupCardWithService() { + updateWallet1State { st -> + st.copy(addBackupButtonLoading = true) + } + + backupService.addBackupCard { result -> + backupService.skipCompatibilityChecks = false + cardSdkConfigRepository.sdk.config.filter.cardIdFilter = null + + updateWallet1State { st -> + st.copy(addBackupButtonLoading = false) + } + + when (result) { + is CompletionResult.Success -> { + // TODO change artwork + state.update { + it.copy( + backupCards = it.backupCards + result.data, + backupCardsNumber = it.backupCardsNumber + 1, + ) + } + setNumberOfBackupCardsWallet1(state.value.backupCardsNumber) + } + is CompletionResult.Failure -> { + when (val error = result.error) { + is TangemSdkError.BackupFailedNotEmptyWallets -> { + updateWallet1State { st -> + st.copy( + dialog = resetBackupCardDialog( + onReset = { resetBackupCard(cardId = error.cardId) }, + onDismiss = { updateWallet1State { it.copy(dialog = null) } }, + ), + ) + } + } + is TangemSdkError.IssuerSignatureLoadingFailed -> { + updateWallet1State { st -> + st.copy( + dialog = backupCardAttestationFailedDialog( + onDismiss = { updateWallet1State { it.copy(dialog = null) } }, + ), + ) + } + } + else -> FirebaseCrashlytics.getInstance().recordException(result.error) + } + } + } + } + } + + private fun resetBackupCard(cardId: String) { + modelScope.launch { + tangemSdkManager.resetToFactorySettings( + cardId = cardId, + allowsRequestAccessCodeFromRepository = false, + ) + } + } + + private fun updateWallet1State(block: (MultiWalletBackupUM.Wallet1) -> MultiWalletBackupUM.Wallet1) { + _uiState.update { state -> + if (state !is MultiWalletBackupUM.Wallet1) return@update state + block(state) + } + } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Dialogs.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Dialogs.kt new file mode 100644 index 0000000000..5b9acbb42e --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Dialogs.kt @@ -0,0 +1,27 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui + +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUMDialog + +internal fun resetBackupCardDialog(onReset: () -> Unit, onDismiss: () -> Unit) = MultiWalletBackupUMDialog( + title = resourceReference(R.string.common_attention), + bodyText = resourceReference(R.string.onboarding_linking_error_card_with_wallets), + confirmText = resourceReference(R.string.common_cancel), + cancelText = resourceReference(R.string.card_settings_action_sheet_reset), + warningCancelColor = true, + onDismiss = onDismiss, + onConfirm = onDismiss, + onCancel = { onReset(); onDismiss() }, +) + +internal fun backupCardAttestationFailedDialog(onDismiss: () -> Unit) = MultiWalletBackupUMDialog( + title = resourceReference(R.string.common_error), + bodyText = resourceReference(R.string.issuer_signature_loading_failed), + confirmText = resourceReference(R.string.common_ok), + cancelText = null, + warningCancelColor = true, + onDismiss = onDismiss, + onConfirm = onDismiss, + onCancel = null, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt index d050a2128f..5c397a1811 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt @@ -4,15 +4,19 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.PrimaryButtonIconEnd -import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.* +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.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUM @@ -38,11 +42,90 @@ fun MultiWalletBackup(state: MultiWalletBackupUM, modifier: Modifier = Modifier) ) } + when (state) { + is MultiWalletBackupUM.Wallet1 -> Wallet1Content(state) + is MultiWalletBackupUM.Wallet2 -> TODO() + } + } + + val dialog = state.dialog + if (dialog != null) { + BasicDialog( + title = dialog.title.resolveReference(), + message = dialog.bodyText.resolveReference(), + confirmButton = DialogButtonUM( + title = dialog.confirmText.resolveReference(), + onClick = dialog.onConfirm, + ), + dismissButton = if (dialog.cancelText != null && dialog.onCancel != null) { + DialogButtonUM( + title = dialog.cancelText.resolveReference(), + warning = dialog.warningCancelColor, + onClick = dialog.onCancel, + ) + } else { + null + }, + onDismissDialog = dialog.onDismiss, + ) + } +} + +@Composable +private fun ColumnScope.Wallet1Content(state: MultiWalletBackupUM.Wallet1) { + Column( + Modifier + .fillMaxWidth() + .padding(start = 32.dp, end = 32.dp, bottom = 74.dp) + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + // artwork + + Text( + text = state.title.resolveReference(), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 16.dp), + ) + + Text( + text = state.bodyText.resolveReference(), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 12.dp), + ) + } + + if (state.showFinalizeButton) { + SecondaryButtonIconEnd( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) + .fillMaxWidth(), + iconResId = R.drawable.ic_tangem_24, + enabled = state.addBackupButtonEnabled, + showProgress = state.addBackupButtonLoading, + text = stringResource(R.string.onboarding_button_add_backup_card), + onClick = state.onAddBackupClick, + ) + + PrimaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = stringResource(R.string.onboarding_button_finalize_backup), + onClick = state.onFinalizeButtonClick, + ) + } else { PrimaryButtonIconEnd( modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) .fillMaxWidth(), iconResId = R.drawable.ic_tangem_24, + enabled = state.addBackupButtonEnabled, + showProgress = state.addBackupButtonLoading, text = stringResource(R.string.onboarding_button_add_backup_card), onClick = state.onAddBackupClick, ) @@ -51,20 +134,33 @@ fun MultiWalletBackup(state: MultiWalletBackupUM, modifier: Modifier = Modifier) modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .fillMaxWidth(), - enabled = state.finalizeButtonEnabled, - text = stringResource(R.string.onboarding_button_finalize_backup), - onClick = state.onFinalizeButtonClick, + text = stringResource(R.string.onboarding_button_skip_backup), + onClick = state.onSkipButtonClick, ) } } @Preview @Composable -private fun Preview() { +private fun PreviewWallet1() { TangemThemePreview { MultiWalletBackup( - state = MultiWalletBackupUM( - finalizeButtonEnabled = true, + state = MultiWalletBackupUM.Wallet1( + title = stringReference("Title"), + bodyText = stringReference("Body body body"), + ), + ) + } +} + +@Preview +@Composable +private fun PreviewWallet2() { + TangemThemePreview { + MultiWalletBackup( + state = MultiWalletBackupUM.Wallet2( + isRing = false, + backupAdded = true, onAddBackupClick = {}, onFinalizeButtonClick = {}, ), diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt new file mode 100644 index 0000000000..6051542ddf --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt @@ -0,0 +1,21 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemThemePreview + +@Composable +fun Wallet1Artwork(modifier: Modifier = Modifier) { + Box(modifier) { + } +} + +@Preview +@Composable +private fun Preview() { + TangemThemePreview { + Wallet1Artwork() + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt index 013235cb7b..cb3351c3db 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt @@ -1,7 +1,45 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state -data class MultiWalletBackupUM( - val finalizeButtonEnabled: Boolean, - val onAddBackupClick: () -> Unit, - val onFinalizeButtonClick: () -> Unit, +import androidx.annotation.IntRange +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference + +@Immutable +sealed class MultiWalletBackupUM { + + abstract val dialog: MultiWalletBackupUMDialog? + + data class Wallet1( + val title: TextReference = stringReference(""), + val bodyText: TextReference = stringReference(""), + val addBackupButtonEnabled: Boolean = true, + val addBackupButtonLoading: Boolean = false, + val showFinalizeButton: Boolean = false, + val oneBackupAdded: Boolean = false, + @IntRange(0, 2) val artworkNumberOfBackupCards: Int = 0, // highlights cards on 0, 1, 2 + val onAddBackupClick: () -> Unit = {}, + val onFinalizeButtonClick: () -> Unit = {}, + val onSkipButtonClick: () -> Unit = {}, + override val dialog: MultiWalletBackupUMDialog? = null, + ) : MultiWalletBackupUM() + + data class Wallet2( + val isRing: Boolean, + val backupAdded: Boolean, + val onAddBackupClick: () -> Unit, + val onFinalizeButtonClick: () -> Unit, + override val dialog: MultiWalletBackupUMDialog? = null, + ) : MultiWalletBackupUM() +} + +data class MultiWalletBackupUMDialog( + val title: TextReference, + val bodyText: TextReference, + val confirmText: TextReference, + val cancelText: TextReference?, + val warningCancelColor: Boolean = false, + val onConfirm: () -> Unit, + val onCancel: (() -> Unit)?, + val onDismiss: () -> Unit, ) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt index 4dcd0f1696..d3b790586d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt @@ -4,13 +4,20 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM -internal fun resetCardDialog(onConfirm: () -> Unit, onDismiss: () -> Unit, onDismissButtonClick: () -> Unit) = +internal fun resetCardDialog(onConfirm: () -> Unit, dismiss: () -> Unit, onDismissButtonClick: () -> Unit) = MultiWalletCreateWalletUM.Dialog( title = resourceReference(R.string.onboarding_activation_error_title), description = resourceReference(R.string.onboarding_activation_error_message), - confirmButtonText = resourceReference(R.string.common_ok), - dismissButtonText = resourceReference(R.string.common_support), - onDismiss = onDismiss, - onConfirm = onConfirm, - onDismissButtonClick = onDismissButtonClick, + confirmButtonText = resourceReference(R.string.common_support), + dismissButtonText = resourceReference(R.string.common_ok), + dismissWarningColor = true, + onDismiss = dismiss, + onConfirm = { + onConfirm() + dismiss() + }, + onDismissButtonClick = { + onDismissButtonClick() + dismiss() + }, ) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt index 0a1992053f..e32cbde7d2 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt @@ -5,6 +5,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType @@ -26,6 +27,7 @@ class MultiWalletCreateWalletModel @Inject constructor( private val tangemSdkManager: TangemSdkManager, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val getCardInfoUseCase: GetCardInfoUseCase, + private val cardRepository: CardRepository, ) : Model() { private val params = paramsContainer.require() @@ -63,6 +65,7 @@ class MultiWalletCreateWalletModel @Inject constructor( ) } + cardRepository.startCardActivation(cardId = result.data.card.cardId) onDone.emit(Unit) // TODO // Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) @@ -82,14 +85,9 @@ class MultiWalletCreateWalletModel @Inject constructor( _uiState.update { state -> state.copy( dialog = resetCardDialog( - onConfirm = { - _uiState.update { it.copy(dialog = null) } - resetCard() - }, - onDismiss = { - _uiState.update { it.copy(dialog = null) } - }, - onDismissButtonClick = ::navigateToSupportScreen, + onConfirm = ::navigateToSupportScreen, + dismiss = { _uiState.update { it.copy(dialog = null) } }, + onDismissButtonClick = ::resetCard, ), ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt index 6b683f4ce6..8fb08b976c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt @@ -32,6 +32,7 @@ fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier ), dismissButton = DialogButtonUM( title = state.dialog.dismissButtonText.resolveReference(), + warning = state.dialog.dismissWarningColor, onClick = state.dialog.onDismissButtonClick, ), onDismissDialog = state.dialog.onDismiss, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt index 9c48ec07a2..c761e1f481 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt @@ -13,6 +13,7 @@ data class MultiWalletCreateWalletUM( val description: TextReference, val dismissButtonText: TextReference, val confirmButtonText: TextReference, + val dismissWarningColor: Boolean = false, val onConfirm: () -> Unit, val onDismissButtonClick: () -> Unit, val onDismiss: () -> Unit, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt index 2510f246f6..1497b73c2d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt @@ -28,6 +28,10 @@ internal class OnboardingMultiWalletModel @Inject constructor( ) init { + // TODO add analytics + // if (!manager.isActivationStarted(notNullCard.cardId)) { + // Analytics.send(Onboarding.Started()) + // } initScreenTitleSub() } @@ -49,11 +53,10 @@ internal class OnboardingMultiWalletModel @Inject constructor( params.titleProvider.changeTitle(title) modelScope.launch { - state - .map { it.currentStep } + state.map { it.currentStep } .collectLatest { step -> - val title = screenTitleByStep(step) - params.titleProvider.changeTitle(title) + val stepTitle = screenTitleByStep(step) + params.titleProvider.changeTitle(stepTitle) } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt index b3c6b709c2..0c3ecc83ea 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt @@ -6,7 +6,6 @@ data class OnboardingMultiWalletState( val currentStep: Step, val currentScanResponse: ScanResponse, ) { - enum class Step { CreateWallet, AddBackupDevice, FinishBackup, Done } diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/BackupServiceHolder.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/BackupServiceHolder.kt new file mode 100644 index 0000000000..85795081fc --- /dev/null +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/BackupServiceHolder.kt @@ -0,0 +1,17 @@ +package com.tangem.sdk.api + +import androidx.activity.ComponentActivity +import com.tangem.TangemSdk +import com.tangem.operations.backup.BackupService +import com.tangem.sdk.extensions.init +import java.lang.ref.WeakReference + +class BackupServiceHolder { + + lateinit var backupService: WeakReference + private set + + fun createAndSetService(tangemSdk: TangemSdk, activity: ComponentActivity) { + backupService = WeakReference(BackupService.init(tangemSdk, activity)) + } +} \ No newline at end of file From cd9e96c526ff30305a2d7726a58b9ee5b95007f8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Nov 2024 10:57:41 +0200 Subject: [PATCH 18/33] Updated on 2026-08-14 --- .../presentation/wallet/state/model/WalletManageButton.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt index 4c811a13ef..0d9c5af5dd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt @@ -44,6 +44,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { text = TextReference.Res(id = R.string.common_buy), iconResId = R.drawable.ic_plus_24, onClick = onClick, + enabled = enabled, dimContent = dimContent, ), ) @@ -64,6 +65,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { text = TextReference.Res(id = R.string.common_send), iconResId = R.drawable.ic_arrow_up_24, onClick = onClick, + enabled = enabled, dimContent = dimContent, ), ) @@ -83,9 +85,10 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_receive), iconResId = R.drawable.ic_arrow_down_24, - dimContent = dimContent, onClick = onClick, onLongClick = onLongClick, + enabled = enabled, + dimContent = dimContent, ), ) @@ -98,6 +101,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { text = TextReference.Res(id = R.string.common_stake), iconResId = R.drawable.ic_staking_24, onClick = onClick, + enabled = enabled, dimContent = dimContent, ), ) @@ -118,6 +122,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { text = TextReference.Res(id = R.string.common_sell), iconResId = R.drawable.ic_currency_24, onClick = onClick, + enabled = enabled, dimContent = dimContent, ), ) @@ -138,6 +143,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { text = TextReference.Res(id = R.string.swapping_swap_action), iconResId = R.drawable.ic_exchange_vertical_24, onClick = onClick, + enabled = enabled, dimContent = dimContent, ), ) From 25bae0ed98f435b45f93030abc16542a35882db8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Nov 2024 13:40:46 +0400 Subject: [PATCH 19/33] Updated on 2026-08-14 --- .../buttons/actions/ActionButtonConfig.kt | 1 + .../ui/components/buttons/actions/Actions.kt | 53 +++++++++++++-- .../implementors/MultiWalletContentLoader.kt | 17 ++++- .../MultiWalletContentLoaderFactory.kt | 6 ++ .../wallet/state/model/WalletManageButton.kt | 10 ++- .../UpdateMultiWalletActionsTransformer.kt | 64 +++++++++++++++++++ .../state/utils/WalletLoadingStateFactory.kt | 24 +++++-- .../MultiWalletActionsSubscriber.kt | 52 +++++++++++++++ .../viewmodels/intents/WalletClickIntents.kt | 10 +++ .../WalletCurrencyActionsClickIntents.kt | 21 ++++++ 10 files changed, 243 insertions(+), 15 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateMultiWalletActionsTransformer.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletActionsSubscriber.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt index c93d92643e..d315a2a715 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt @@ -23,4 +23,5 @@ data class ActionButtonConfig( val onLongClick: (() -> TextReference?)? = null, val enabled: Boolean = true, val dimContent: Boolean = false, + val isInProgress: Boolean = false, ) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index 60bc6e7e12..728eb3a49c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -8,8 +8,9 @@ import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Icon -import androidx.compose.material.Text +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 @@ -77,7 +78,6 @@ fun ActionButton( ) } -@OptIn(ExperimentalFoundationApi::class) @Composable private fun Button( config: ActionButtonConfig, @@ -89,12 +89,33 @@ private fun Button( targetValue = if (config.enabled) color else TangemTheme.colors.button.disabled, label = "Update background color", ) - val context = LocalContext.current - Row( + + Box( modifier = modifier .heightIn(min = TangemTheme.dimens.size36) .clip(shape) - .background(color = backgroundColor) + .background(color = backgroundColor), + ) { + Content(config = config) + + if (config.isInProgress) { + Loading( + backgroundColor = backgroundColor, + modifier = Modifier + .matchParentSize() + .align(alignment = Alignment.Center), + ) + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun Content(config: ActionButtonConfig) { + val context = LocalContext.current + + Row( + modifier = Modifier .combinedClickable( enabled = config.enabled, onClick = config.onClick, @@ -149,6 +170,19 @@ private fun Button( } } +@Composable +private fun Loading(backgroundColor: Color, modifier: Modifier = Modifier) { + Box( + modifier = modifier.background(color = backgroundColor), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator( + modifier = Modifier.size(TangemTheme.dimens.size24), + color = TangemTheme.colors.icon.accent, + ) + } +} + @Preview(group = "RoundedActionButton", showBackground = true) @Preview(group = "RoundedActionButton", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable @@ -188,5 +222,12 @@ private class ActionStateProvider : CollectionPreviewParameterProvider { - return listOf( + return listOfNotNull( MultiWalletTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -50,6 +55,16 @@ internal class MultiWalletContentLoader( getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, ), + createMultiWalletButtonsSubscriber(), ) } + + private fun createMultiWalletButtonsSubscriber(): MultiWalletActionsSubscriber? { + return MultiWalletActionsSubscriber( + userWallet = userWallet, + rampStateManager = rampStateManager, + stateController = stateHolder, + ) + .takeIf { walletFeatureToggles.isMainActionButtonsEnabled } + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 7a70b865b2..5bc11a8b00 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet @@ -11,6 +12,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject @@ -26,6 +28,8 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, + private val rampStateManager: RampStateManager, + private val walletFeatureToggles: WalletFeatureToggles, ) { fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader { @@ -41,6 +45,8 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, applyTokenListSortingUseCase = applyTokenListSortingUseCase, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, + rampStateManager = rampStateManager, + walletFeatureToggles = walletFeatureToggles, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt index 0d9c5af5dd..515041aec5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt @@ -38,6 +38,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { override val enabled: Boolean, override val dimContent: Boolean, override val onClick: () -> Unit, + val isInProgress: Boolean = false, ) : WalletManageButton( config = ActionButtonConfig( @@ -46,6 +47,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { onClick = onClick, enabled = enabled, dimContent = dimContent, + isInProgress = isInProgress, ), ) @@ -85,10 +87,10 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_receive), iconResId = R.drawable.ic_arrow_down_24, - onClick = onClick, - onLongClick = onLongClick, enabled = enabled, dimContent = dimContent, + onClick = onClick, + onLongClick = onLongClick, ), ) @@ -117,6 +119,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { override val enabled: Boolean, override val dimContent: Boolean, override val onClick: () -> Unit, + val isInProgress: Boolean = false, ) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_sell), @@ -124,6 +127,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { onClick = onClick, enabled = enabled, dimContent = dimContent, + isInProgress = isInProgress, ), ) @@ -138,6 +142,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { override val enabled: Boolean, override val dimContent: Boolean, override val onClick: () -> Unit, + val isInProgress: Boolean = false, ) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.swapping_swap_action), @@ -145,6 +150,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { onClick = onClick, enabled = enabled, dimContent = dimContent, + isInProgress = isInProgress, ), ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateMultiWalletActionsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateMultiWalletActionsTransformer.kt new file mode 100644 index 0000000000..a59a4bf108 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateMultiWalletActionsTransformer.kt @@ -0,0 +1,64 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers + +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toPersistentList +import timber.log.Timber + +internal class UpdateMultiWalletActionsTransformer( + userWalletId: UserWalletId, + private val buyStatus: Lce, + private val sellStatus: Lce, + private val swapStatus: Lce, +) : WalletStateTransformer(userWalletId = userWalletId) { + + override fun transform(prevState: WalletState): WalletState { + return when (prevState) { + is WalletState.MultiCurrency.Content -> { + prevState.copy(buttons = prevState.buttons.updateButtons()) + } + is WalletState.MultiCurrency.Locked -> { + Timber.w("Impossible to load primary currency status for locked wallet") + prevState + } + is WalletState.SingleCurrency -> { + Timber.w("Impossible to load crypto currency actions for multi-currency wallet") + prevState + } + is WalletState.Visa -> { + Timber.w("Impossible to load crypto currency actions for VISA wallet") + prevState + } + } + } + + private fun PersistentList.updateButtons(): PersistentList { + return map { + when (it) { + is WalletManageButton.Buy -> { + it.copy( + enabled = buyStatus.isContent(), + dimContent = !buyStatus.isContent(), + ) + } + is WalletManageButton.Sell -> { + it.copy( + enabled = sellStatus.isContent(), + dimContent = !sellStatus.isContent(), + ) + } + is WalletManageButton.Swap -> { + it.copy( + enabled = swapStatus.isContent(), + dimContent = !swapStatus.isContent(), + ) + } + else -> it + } + } + .toPersistentList() + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt index 2b622d7571..6b9dda9803 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt @@ -37,7 +37,7 @@ internal class WalletLoadingStateFactory( return WalletState.MultiCurrency.Content( pullToRefreshConfig = createPullToRefreshConfig(), walletCardState = userWallet.toLoadingWalletCardState(), - buttons = createMultiWalletActions(), + buttons = createMultiWalletActions(userWallet), warnings = persistentListOf(), bottomSheetConfig = null, tokensListState = WalletTokensListState.ContentState.Loading, @@ -65,7 +65,7 @@ internal class WalletLoadingStateFactory( return WalletState.Visa.Content( pullToRefreshConfig = createPullToRefreshConfig(), walletCardState = userWallet.toLoadingWalletCardState(), - buttons = createMultiWalletActions(), + buttons = createMultiWalletActions(userWallet), warnings = persistentListOf(), bottomSheetConfig = null, balancesAndLimitBlockState = BalancesAndLimitsBlockState.Loading, @@ -93,13 +93,25 @@ internal class WalletLoadingStateFactory( ) } - private fun createMultiWalletActions(): PersistentList { + private fun createMultiWalletActions(userWallet: UserWallet): PersistentList { if (!walletFeatureToggles.isMainActionButtonsEnabled) return persistentListOf() return persistentListOf( - WalletManageButton.Buy(enabled = false, dimContent = true, onClick = { }), - WalletManageButton.Swap(enabled = false, dimContent = true, onClick = { }), - WalletManageButton.Sell(enabled = false, dimContent = true, onClick = { }), + WalletManageButton.Buy( + enabled = false, + dimContent = true, + onClick = { clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId) }, + ), + WalletManageButton.Swap( + enabled = false, + dimContent = true, + onClick = { clickIntents.onMultiWalletSwapClick(userWalletId = userWallet.walletId) }, + ), + WalletManageButton.Sell( + enabled = false, + dimContent = true, + onClick = { clickIntents.onMultiWalletSellClick(userWalletId = userWallet.walletId) }, + ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletActionsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletActionsSubscriber.kt new file mode 100644 index 0000000000..266bb7b67c --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletActionsSubscriber.kt @@ -0,0 +1,52 @@ +package com.tangem.feature.wallet.presentation.wallet.subscribers + +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.UpdateMultiWalletActionsTransformer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.onEach + +/** + * Multi-currency wallet subscriber for actions state updating + * + * @property userWallet user wallet + * @property rampStateManager ramp state manager + * @property stateController state controller + * +[REDACTED_AUTHOR] + */ +internal class MultiWalletActionsSubscriber( + private val userWallet: UserWallet, + private val rampStateManager: RampStateManager, + private val stateController: WalletStateController, +) : WalletSubscriber() { + + override fun create(coroutineScope: CoroutineScope): Flow<*> { + return combine( + flow = rampStateManager.getBuyInitializationStatus(), + flow2 = rampStateManager.getSellInitializationStatus(), + flow3 = rampStateManager.getSwapInitializationStatus(userWalletId = userWallet.walletId), + transform = ::RampStatuses, + ) + .onEach { statuses -> + stateController.update( + UpdateMultiWalletActionsTransformer( + userWalletId = userWallet.walletId, + buyStatus = statuses.buy, + sellStatus = statuses.sell, + swapStatus = statuses.swap, + ), + ) + } + } + + private data class RampStatuses( + val buy: Lce, + val sell: Lce, + val swap: Lce, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt index 0c716472a9..f40ea9b059 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt @@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.settings.NeverToShowWalletsScrollPreview import com.tangem.domain.tokens.FetchCardTokenListUseCase import com.tangem.domain.tokens.FetchCurrencyStatusUseCase @@ -20,6 +21,8 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetToken import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch import javax.inject.Inject @@ -41,6 +44,7 @@ internal class WalletClickIntents @Inject constructor( private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val neverToShowWalletsScrollPreview: NeverToShowWalletsScrollPreview, + private val rampStateManager: RampStateManager, private val dispatchers: CoroutineDispatcherProvider, ) : BaseWalletClickIntents(), WalletCardClickIntents by walletCardClickIntentsImplementor, @@ -116,6 +120,12 @@ internal class WalletClickIntents @Inject constructor( fetchTokenListUseCase(userWalletId = userWallet.walletId, mode = RefreshMode.FULL) } + listOf( + async { rampStateManager.fetchBuyServiceData() }, + async { rampStateManager.fetchSellServiceData() }, + ) + .awaitAll() + maybeFetchResult.onLeft { stateHolder.update( SetTokenListErrorTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 21fcf81bf1..dba1cd4f91 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -81,6 +81,12 @@ interface WalletCurrencyActionsClickIntents { fun onExploreClick() fun onAnalyticsClick(cryptoCurrencyStatus: CryptoCurrencyStatus) + + fun onMultiWalletBuyClick(userWalletId: UserWalletId) + + fun onMultiWalletSellClick(userWalletId: UserWalletId) + + fun onMultiWalletSwapClick(userWalletId: UserWalletId) } @Suppress("LongParameterList", "LargeClass") @@ -432,6 +438,21 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( } } + override fun onMultiWalletSellClick(userWalletId: UserWalletId) { + // TODO [REDACTED_JIRA] + appRouter.push(route = AppRoute.SellCrypto(userWalletId = userWalletId)) + } + + override fun onMultiWalletSwapClick(userWalletId: UserWalletId) { + // TODO [REDACTED_JIRA] + // appRouter.push(route = AppRoute.SwapCrypto(userWalletId = userWalletId)) + } + + override fun onMultiWalletBuyClick(userWalletId: UserWalletId) { + // TODO [REDACTED_JIRA] + appRouter.push(route = AppRoute.BuyCrypto(userWalletId = userWalletId)) + } + private fun openExplorer() { val userWalletId = stateHolder.getSelectedWalletId() From e2b5e5b07b107e18c853bb7f0f99f95cd4035150 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 9 Nov 2024 08:43:41 +0300 Subject: [PATCH 20/33] Updated on 2026-08-14 --- .../routing/impl/DefaultRoutingComponent.kt | 2 +- .../wallets/usecase/GetCardImageUseCase.kt | 4 +- features/onboarding-v2/impl/build.gradle.kts | 1 + .../DefaultOnboardingMultiWalletComponent.kt | 21 +- .../impl/child/backup/ui/MultiWalletBackup.kt | 20 +- .../impl/child/backup/ui/Wallet1Artwork.kt | 21 - .../ui/MultiWalletCreateWallet.kt | 22 +- .../v2/multiwallet/impl/model/Dialogs.kt | 16 - .../impl/model/OnboardingMultiWalletModel.kt | 25 +- .../impl/ui/OnboardingMultiWallet.kt | 78 ++- .../v2/multiwallet/impl/ui/WalletArtwork.kt | 462 ++++++++++++++++++ .../impl/ui/state/OnboardingMultiWalletUM.kt | 20 +- 12 files changed, 539 insertions(+), 153 deletions(-) delete mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt delete mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Dialogs.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt diff --git a/app/src/main/java/com/tangem/tap/routing/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/impl/DefaultRoutingComponent.kt index c3cd524d98..77283b4c75 100644 --- a/app/src/main/java/com/tangem/tap/routing/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/impl/DefaultRoutingComponent.kt @@ -27,7 +27,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( override val stack: Value> = childStack( source = navigationProvider.getOrCreateTyped(), - serializer = AppRoute.serializer(), + serializer = AppRoute.serializer(), // TODO Maybe set this to null for AppRoute.Onboarding case. Need to check initialConfiguration = getInitialRoute(), handleBackButton = false, childFactory = ::child, diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt index 3d92875a5b..67080c0682 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt @@ -38,6 +38,8 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV } } + fun getDefaultFallbackUrl(): String = Artwork.DEFAULT_IMG_URL + private fun getFallbackArtworkUrl(cardId: String): String { return when { cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL @@ -45,7 +47,7 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV else -> when (TwinsHelper.getTwinCardNumber(cardId)) { TwinCardNumber.First -> Artwork.TWIN_CARD_1_URL TwinCardNumber.Second -> Artwork.TWIN_CARD_2_URL - else -> Artwork.DEFAULT_IMG_URL + else -> getDefaultFallbackUrl() } } } diff --git a/features/onboarding-v2/impl/build.gradle.kts b/features/onboarding-v2/impl/build.gradle.kts index ba4fb5017f..fac172d9d1 100644 --- a/features/onboarding-v2/impl/build.gradle.kts +++ b/features/onboarding-v2/impl/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(projects.domain.feedback) implementation(projects.domain.core) implementation(projects.domain.card) + implementation(projects.domain.wallets) /** Tangem libraries */ implementation(projects.libs.tangemSdkApi) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt index 9b8676533f..846e1f0d53 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt @@ -3,6 +3,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation @@ -21,6 +22,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWall import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState +import com.tangem.features.onboarding.v2.multiwallet.impl.ui.OnboardingMultiWallet import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -95,13 +97,20 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor @Composable override fun Content(modifier: Modifier) { val stackState by childStack.subscribeAsState() + val state by model.uiState.collectAsStateWithLifecycle() - Children( - stack = stackState, - animation = stackAnimation(slide()), - ) { - it.instance.Content(Modifier) - } + OnboardingMultiWallet( + state = state, + modifier = modifier, + childContent = { mdfr -> + Children( + stack = stackState, + animation = stackAnimation(slide()), + ) { + it.instance.Content(mdfr) + } + }, + ) } @AssistedFactory diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt index 5c397a1811..a8a1e22522 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt @@ -1,14 +1,10 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -26,22 +22,8 @@ fun MultiWalletBackup(state: MultiWalletBackupUM, modifier: Modifier = Modifier) Column( modifier = modifier .fillMaxSize() - .navigationBarsPadding() - .verticalScroll(rememberScrollState()), + .navigationBarsPadding(), ) { - Box( - Modifier - .fillMaxWidth() - .weight(1f), - ) { - Box( - Modifier - .align(Alignment.Center) - .size(100.dp) - .background(Color.Black), - ) - } - when (state) { is MultiWalletBackupUM.Wallet1 -> Wallet1Content(state) is MultiWalletBackupUM.Wallet2 -> TODO() diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt deleted file mode 100644 index 6051542ddf..0000000000 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui - -import androidx.compose.foundation.layout.Box -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.res.TangemThemePreview - -@Composable -fun Wallet1Artwork(modifier: Modifier = Modifier) { - Box(modifier) { - } -} - -@Preview -@Composable -private fun Preview() { - TangemThemePreview { - Wallet1Artwork() - } -} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt index 8fb08b976c..b20e13c3af 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt @@ -1,13 +1,8 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -42,22 +37,9 @@ fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier Column( modifier = modifier .fillMaxSize() - .navigationBarsPadding() - .verticalScroll(rememberScrollState()), + .navigationBarsPadding(), + verticalArrangement = Arrangement.Bottom, ) { - Box( - Modifier - .fillMaxWidth() - .weight(1f), - ) { - Box( - Modifier - .align(Alignment.Center) - .size(100.dp) - .background(Color.Black), - ) - } - PrimaryButtonIconEnd( modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Dialogs.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Dialogs.kt deleted file mode 100644 index f6905984f6..0000000000 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Dialogs.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.features.onboarding.v2.multiwallet.impl.model - -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.features.onboarding.v2.impl.R -import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM - -internal fun resetCardDialog(onConfirm: () -> Unit, onDismiss: () -> Unit, onDismissButtonClick: () -> Unit) = - OnboardingMultiWalletUM.Dialog( - title = resourceReference(R.string.onboarding_activation_error_title), - description = resourceReference(R.string.onboarding_activation_error_message), - confirmButtonText = resourceReference(R.string.common_ok), - dismissButtonText = resourceReference(R.string.common_support), - onDismiss = onDismiss, - onConfirm = onConfirm, - onDismissButtonClick = onDismissButtonClick, - ) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt index 1497b73c2d..5a8c2f5fca 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt @@ -4,11 +4,11 @@ import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.wallets.usecase.GetCardImageUseCase import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject @@ -17,8 +17,9 @@ internal class OnboardingMultiWalletModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, ) : Model() { - private val params = paramsContainer.require() + private val getCardImageUseCase = GetCardImageUseCase() + private val _uiState = MutableStateFlow(OnboardingMultiWalletUM()) val state = MutableStateFlow( OnboardingMultiWalletState( @@ -27,12 +28,15 @@ internal class OnboardingMultiWalletModel @Inject constructor( ), ) + val uiState = _uiState.asStateFlow() + init { // TODO add analytics // if (!manager.isActivationStarted(notNullCard.cardId)) { // Analytics.send(Onboarding.Started()) // } initScreenTitleSub() + loadCardArtwork() } private fun getInitialStep(): OnboardingMultiWalletState.Step { @@ -60,4 +64,17 @@ internal class OnboardingMultiWalletModel @Inject constructor( } } } + + private fun loadCardArtwork() { + modelScope.launch { + val artwork = + getCardImageUseCase.invoke(params.scanResponse.card.cardId, params.scanResponse.card.cardPublicKey) + + if (artwork != getCardImageUseCase.getDefaultFallbackUrl()) { + _uiState.update { + it.copy(artworkUrl = artwork) + } + } + } + } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt index d2de011033..9a3b0efcec 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt @@ -1,79 +1,63 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.ui +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource +import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.BasicDialog -import com.tangem.core.ui.components.DialogButtonUM -import com.tangem.core.ui.components.PrimaryButtonIconEnd -import com.tangem.core.ui.components.SecondaryButton -import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM @Composable -internal fun OnboardingMultiWallet(state: OnboardingMultiWalletUM, modifier: Modifier = Modifier) { - if (state.dialog != null) { - BasicDialog( - title = state.dialog.title.resolveReference(), - message = state.dialog.description.resolveReference(), - confirmButton = DialogButtonUM( - title = state.dialog.confirmButtonText.resolveReference(), - onClick = state.dialog.onConfirm, - ), - dismissButton = DialogButtonUM( - title = state.dialog.dismissButtonText.resolveReference(), - onClick = state.dialog.onDismissButtonClick, - ), - onDismissDialog = state.dialog.onDismiss, - ) - } - +@Suppress("MagicNumber") +internal fun OnboardingMultiWallet( + state: OnboardingMultiWalletUM, + modifier: Modifier = Modifier, + childContent: @Composable (Modifier) -> Unit = {}, +) { Column( modifier = modifier .fillMaxSize() - .navigationBarsPadding() .verticalScroll(rememberScrollState()), ) { - Box(Modifier.weight(1f)) - - PrimaryButtonIconEnd( + WalletArtworks( + url = state.artworkUrl, modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) + .weight(.56f) + .padding(horizontal = 34.dp) + .padding(top = 20.dp) .fillMaxWidth(), - iconResId = R.drawable.ic_tangem_24, - text = stringResource(R.string.onboarding_create_wallet_button_create_wallet), - onClick = state.onCreateWalletClick, + state = WalletArtworksState.Folded, ) - SecondaryButton( - modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) - .fillMaxWidth(), - text = stringResource(R.string.onboarding_create_wallet_options_button_options), - onClick = state.onOtherOptionsClick, - ) + Box( + modifier = Modifier.weight(.44f), + contentAlignment = Alignment.BottomStart, + ) { + childContent(Modifier) + } } } -@Preview +@Preview(showBackground = true, widthDp = 360, heightDp = 640) @Composable private fun Preview() { TangemThemePreview { OnboardingMultiWallet( - state = OnboardingMultiWalletUM( - onCreateWalletClick = {}, - showSeedPhraseOption = true, - onBack = {}, - onOtherOptionsClick = {}, - dialog = null, - ), + state = OnboardingMultiWalletUM(artworkUrl = null), + childContent = { md -> + Box( + modifier = md + .fillMaxWidth() + .height(200.dp) + .background(Color.DarkGray), + ) + }, ) } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt new file mode 100644 index 0000000000..fa5c1643a7 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt @@ -0,0 +1,462 @@ +@file:Suppress("MagicNumber", "UnnecessaryParentheses", "CyclomaticComplexMethod", "LongMethod") +package com.tangem.features.onboarding.v2.multiwallet.impl.ui + +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach +import androidx.compose.ui.zIndex +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R +import kotlinx.coroutines.delay +import java.util.concurrent.TimeUnit + +sealed class WalletArtworksState { + data object Folded : WalletArtworksState() + + data object Fan : WalletArtworksState() + + data class Stack( + val threeCards: Boolean = true, + ) : WalletArtworksState() + + data class Unfold( + val step: Step = Step.First, + ) : WalletArtworksState() { + enum class Step { + First, Second, Third + } + } + + data class Leapfrog( + val threeCards: Boolean = true, + val step: Step = Step.Second, + ) : WalletArtworksState() { + enum class Step { + Second, Third + } + } +} + +@Composable +fun WalletArtworks(url: String?, state: WalletArtworksState, modifier: Modifier = Modifier) { + BoxWithConstraints( + modifier + .heightIn(min = 180.dp) + .widthIn(max = 450.dp), + ) { + val animationWidth = maxWidth + val density = LocalDensity.current.density + + var animationState by remember { + mutableStateOf( + state.toTransitionSetState( + maxWidthDp = animationWidth.value, + density = density, + maxHeightDp = maxHeight.value, + ).first(), + ) + } + + val transition1 = updateTransition(animationState.walletCard1, label = "transition") + val transition2 = updateTransition(animationState.walletCard2, label = "transition") + val transition3 = updateTransition(animationState.walletCard3, label = "transition") + + LaunchedEffect(state) { + val animationSetState = state.toTransitionSetState( + maxWidthDp = animationWidth.value, + density = density, + maxHeightDp = maxHeight.value, + ) + animationSetState.fastForEach { + animationState = it + val maxTime = maxOf( + transition1.totalDurationNanos, + transition2.totalDurationNanos, + transition3.totalDurationNanos, + ) + delay(TimeUnit.NANOSECONDS.toMillis(maxTime)) + } + } + + AnimatedWalletCards( + url = url, + modifier = Modifier + .widthIn(max = 450.dp) + .matchParentSize(), + transition1 = transition1, + transition2 = transition2, + transition3 = transition3, + ) + } +} + +private data class CardsTransitionState( + val walletCard1: WalletCardTransitionState, + val walletCard2: WalletCardTransitionState, + val walletCard3: WalletCardTransitionState, +) + +private data class WalletCardTransitionState( + val alpha: Float = 1f, + val xTranslation: Float = 0f, + val yTranslation: Float = 0f, + val xScale: Float = 1f, + val yScale: Float = 1f, + val rotation: Float = 0f, + val zIndex: Float = 0f, +) + +@Composable +private fun AnimatedWalletCards( + url: String?, + transition1: Transition, + transition2: Transition, + transition3: Transition, + modifier: Modifier = Modifier, +) { + val alpha1 by transition1.animateFloat(label = "alpha1") { it.alpha } + val translationX1 by transition1.animateFloat(label = "translationX1") { it.xTranslation } + val translationY1 by transition1.animateFloat(label = "translationY1") { it.yTranslation } + val rotation1 by transition1.animateFloat(label = "rotation1") { it.rotation } + val scaleX1 by transition1.animateFloat(label = "scaleX1") { it.xScale } + val scaleY1 by transition1.animateFloat(label = "scaleY1") { it.yScale } + val zIndex1 = transition1.currentState.zIndex + + val alpha2 by transition2.animateFloat(label = "alpha2") { it.alpha } + val translationX2 by transition2.animateFloat(label = "translationX2") { it.xTranslation } + val translationY2 by transition2.animateFloat(label = "translationY2") { it.yTranslation } + val rotation2 by transition2.animateFloat(label = "rotation2") { it.rotation } + val scaleX2 by transition2.animateFloat(label = "scaleX2") { it.xScale } + val scaleY2 by transition2.animateFloat(label = "scaleY2") { it.yScale } + val zIndex2 = transition2.currentState.zIndex + + val alpha3 by transition3.animateFloat(label = "alpha3") { it.alpha } + val translationX3 by transition3.animateFloat(label = "translationX3") { it.xTranslation } + val translationY3 by transition3.animateFloat(label = "translationY3") { it.yTranslation } + val rotation3 by transition3.animateFloat(label = "rotation3") { it.rotation } + val scaleX3 by transition3.animateFloat(label = "scaleX3") { it.xScale } + val scaleY3 by transition3.animateFloat(label = "scaleY3") { it.yScale } + val zIndex3 = transition3.targetState.zIndex + + Box(modifier) { + WalletCard( + modifier = Modifier + .zIndex(zIndex3) + .matchParentSize() + .graphicsLayer { + translationX = translationX3 + translationY = translationY3 + } + .graphicsLayer { + alpha = alpha3 + scaleX = scaleX3 + scaleY = scaleY3 + rotationZ = rotation3 + }, + url = url, + ) + + WalletCard( + modifier = Modifier + .zIndex(zIndex2) + .matchParentSize() + .graphicsLayer { + translationX = translationX2 + translationY = translationY2 + } + .graphicsLayer { + alpha = alpha2 + rotationZ = rotation2 + + scaleX = scaleX2 + scaleY = scaleY2 + }, + url = url, + ) + + WalletCard( + modifier = Modifier + .zIndex(zIndex1) + .matchParentSize() + .graphicsLayer { + scaleX = scaleX1 + scaleY = scaleY1 + } + .graphicsLayer { + alpha = alpha1 + translationX = translationX1 + translationY = translationY1 + rotationZ = rotation1 + }, + url = url, + ) + } +} + +@Composable +private fun WalletCard(url: String?, modifier: Modifier = Modifier) { + AsyncImage( + modifier = modifier, + model = ImageRequest.Builder(LocalContext.current) + .data(url) + .crossfade(true) + .build(), + placeholder = painterResource(R.drawable.card_placeholder_black), + error = painterResource(R.drawable.card_placeholder_black), + fallback = painterResource(R.drawable.card_placeholder_black), + contentDescription = null, + ) +} + +private fun WalletArtworksState.toTransitionSetState(maxWidthDp: Float, maxHeightDp: Float, density: Float) = + when (this) { + is WalletArtworksState.Folded -> listOf( + CardsTransitionState( + WalletCardTransitionState(), + WalletCardTransitionState(), + WalletCardTransitionState(), + ), + ) + is WalletArtworksState.Stack -> listOf( + CardsTransitionState( + walletCard1 = WalletCardTransitionState(), + walletCard2 = WalletCardTransitionState(), + walletCard3 = WalletCardTransitionState(), + ), + CardsTransitionState( + walletCard1 = WalletCardTransitionState().copy( + alpha = 1f, + xTranslation = 0f, + yTranslation = 0f, + rotation = 0f, + xScale = 1f, + yScale = 1f, + ), + walletCard2 = WalletCardTransitionState().copy( + alpha = 0.65f, + xTranslation = 0f, + yTranslation = ((maxHeightDp * (1 - 0.83f)) / 4 + 12) * density, + rotation = 0f, + xScale = 0.83f, + yScale = 0.83f, + ), + walletCard3 = WalletCardTransitionState().copy( + alpha = if (threeCards) 0.55f else 0f, + xTranslation = 0f, + yTranslation = (maxHeightDp * (1 - 0.72f) / 4 + 24) * density, + rotation = 0f, + xScale = 0.72f, + yScale = 0.72f, + ), + ), + ) + is WalletArtworksState.Unfold -> listOfNotNull( + CardsTransitionState( + walletCard1 = WalletCardTransitionState(), + walletCard2 = WalletCardTransitionState().copy( + alpha = 0.65f, + ), + walletCard3 = WalletCardTransitionState().copy( + alpha = 0.55f, + ), + ).takeIf { step == WalletArtworksState.Unfold.Step.First }, + CardsTransitionState( + walletCard1 = WalletCardTransitionState().copy( + rotation = 5f, + ), + walletCard2 = WalletCardTransitionState().copy( + alpha = when (step) { + WalletArtworksState.Unfold.Step.First -> 0.65f + WalletArtworksState.Unfold.Step.Second -> 1f + WalletArtworksState.Unfold.Step.Third -> 1f + }, + rotation = -6f, + xTranslation = 0f, + yTranslation = -(maxHeightDp * (1 - 0.88f) / 4) * density, + xScale = 0.88f, + yScale = 0.88f, + ), + walletCard3 = WalletCardTransitionState().copy( + alpha = when (step) { + WalletArtworksState.Unfold.Step.First -> 0.55f + WalletArtworksState.Unfold.Step.Second -> 0.55f + WalletArtworksState.Unfold.Step.Third -> 1f + }, + rotation = -16f, + xTranslation = 0f, + yTranslation = -(maxHeightDp * (1 - 0.81f) / 4 + 2) * density, + xScale = 0.81f, + yScale = 0.81f, + ), + ), + ) + WalletArtworksState.Fan -> { + listOf( + CardsTransitionState( + walletCard1 = WalletCardTransitionState().copy( + xTranslation = maxWidthDp * 0.4f * density, + yTranslation = 35 * density, + rotation = 75f, + xScale = .85f, + yScale = .85f, + ), + walletCard2 = WalletCardTransitionState().copy( + xTranslation = maxWidthDp * -0.4f * density, + yTranslation = -5 * density, + rotation = 75f, + xScale = .85f, + yScale = .85f, + ), + walletCard3 = WalletCardTransitionState().copy( + rotation = 105f, + xScale = .85f, + yScale = .85f, + ), + ), + ) + } + is WalletArtworksState.Leapfrog -> { + val translationY = -(maxHeightDp / 2) * density + val secondCardPlace = WalletCardTransitionState().copy( + alpha = 0.65f, + xTranslation = 0f, + yTranslation = ((maxHeightDp * (1 - 0.83f)) / 4 + 12) * density, + rotation = 0f, + xScale = 0.83f, + yScale = 0.83f, + ) + val thirdCardPlace = WalletCardTransitionState().copy( + alpha = if (threeCards) 0.55f else 0f, + xTranslation = 0f, + yTranslation = (maxHeightDp * (1 - 0.72f) / 4 + 24) * density, + rotation = 0f, + xScale = 0.72f, + yScale = 0.72f, + ) + + if (step == WalletArtworksState.Leapfrog.Step.Second) { + listOf( + CardsTransitionState( + walletCard1 = WalletCardTransitionState(), + walletCard2 = secondCardPlace, + walletCard3 = thirdCardPlace, + ), + CardsTransitionState( + walletCard1 = WalletCardTransitionState().copy( + yTranslation = translationY, + ), + walletCard2 = WalletCardTransitionState().copy( + alpha = 1f, + zIndex = 4f, + ), + walletCard3 = thirdCardPlace, + ), + CardsTransitionState( + walletCard1 = WalletCardTransitionState().copy( + zIndex = 1f, + ), + walletCard2 = WalletCardTransitionState().copy( + zIndex = 4f, + ), + walletCard3 = secondCardPlace, + ), + ) + } else { + listOf( + CardsTransitionState( + walletCard1 = WalletCardTransitionState().copy( + zIndex = 2f, + ), + walletCard2 = WalletCardTransitionState().copy( + zIndex = 3f, + ), + walletCard3 = secondCardPlace, + ), + CardsTransitionState( + walletCard1 = WalletCardTransitionState().copy( + yTranslation = translationY, + ), + walletCard2 = WalletCardTransitionState().copy( + zIndex = 3f, + yTranslation = translationY, + ), + walletCard3 = WalletCardTransitionState().copy( + alpha = 1f, + zIndex = 4f, + ), + ), + CardsTransitionState( + walletCard1 = WalletCardTransitionState().copy( + zIndex = 1f, + ), + walletCard2 = WalletCardTransitionState().copy( + zIndex = 4f, + ), + walletCard3 = WalletCardTransitionState().copy( + zIndex = 5f, + ), + ), + ) + } + } + } + +@Preview(showBackground = true, widthDp = 360, heightDp = 640) +@Composable +private fun Preview() { + TangemThemePreview { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + var state: WalletArtworksState by remember { mutableStateOf(WalletArtworksState.Folded) } + + WalletArtworks( + url = null, + state = state, + modifier = Modifier.fillMaxWidth(), + ) + + var index by remember { mutableIntStateOf(0) } + + Button( + modifier = Modifier + .zIndex(5f) + .align(Alignment.TopStart), + onClick = { + val list = listOf( + WalletArtworksState.Folded, + WalletArtworksState.Stack(), + WalletArtworksState.Fan, + WalletArtworksState.Unfold(step = WalletArtworksState.Unfold.Step.First), + WalletArtworksState.Unfold(step = WalletArtworksState.Unfold.Step.Second), + WalletArtworksState.Unfold(step = WalletArtworksState.Unfold.Step.Third), + WalletArtworksState.Stack(), + WalletArtworksState.Leapfrog(step = WalletArtworksState.Leapfrog.Step.Second), + WalletArtworksState.Leapfrog(step = WalletArtworksState.Leapfrog.Step.Third), + ) + + state = list[index % list.size] + index++ + }, + ) { + Text("Animate") + } + } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt index 8ae99c8da0..1e4884ea7a 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt @@ -1,21 +1,5 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.ui.state -import com.tangem.core.ui.extensions.TextReference - internal data class OnboardingMultiWalletUM( - val onCreateWalletClick: () -> Unit, - val showSeedPhraseOption: Boolean, - val onOtherOptionsClick: () -> Unit, - val onBack: () -> Unit, - val dialog: Dialog?, -) { - data class Dialog( - val title: TextReference, - val description: TextReference, - val dismissButtonText: TextReference, - val confirmButtonText: TextReference, - val onConfirm: () -> Unit, - val onDismissButtonClick: () -> Unit, - val onDismiss: () -> Unit, - ) -} \ No newline at end of file + val artworkUrl: String? = null, +) \ No newline at end of file From 81b2f9e4a394bc0c6d235852bbe90a4e84d1d269 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 9 Nov 2024 13:53:15 +0400 Subject: [PATCH 21/33] Updated on 2026-08-14 --- .../tangem/tap/di/domain/SwapDomainModule.kt | 23 +++ features/onramp/impl/build.gradle.kts | 3 + .../AvailableSwapPairsComponent.kt | 30 +++ .../DefaultAvailableSwapPairsComponent.kt | 38 ++++ .../di/AvailableSwapPairsComponentModule.kt | 20 ++ .../di/AvailableSwapPairsModelModule.kt | 20 ++ .../LoadingTokenListItemConverter.kt | 26 +++ .../SetLoadingTokenItemsTransformer.kt | 23 +++ .../model/AvailableSwapPairsModel.kt | 174 ++++++++++++++++++ .../swap/domain/GetAvailablePairsUseCase.kt | 18 ++ 10 files changed, 375 insertions(+) create mode 100644 app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/AvailableSwapPairsComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/DefaultAvailableSwapPairsComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsComponentModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingTokenListItemConverter.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetLoadingTokenItemsTransformer.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/GetAvailablePairsUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt new file mode 100644 index 0000000000..e94429b646 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt @@ -0,0 +1,23 @@ +package com.tangem.tap.di.domain + +import com.tangem.feature.swap.domain.GetAvailablePairsUseCase +import com.tangem.feature.swap.domain.api.SwapRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +/** +[REDACTED_AUTHOR] + */ +@Module +@InstallIn(SingletonComponent::class) +internal object SwapDomainModule { + + @Provides + @Singleton + fun provideGetAvailablePairsUseCase(swapRepository: SwapRepository): GetAvailablePairsUseCase { + return GetAvailablePairsUseCase(swapRepository = swapRepository) + } +} \ No newline at end of file diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index be47292f09..0aafc9b959 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -14,6 +14,9 @@ android { dependencies { /** Project - API */ implementation(projects.features.onramp.api) + implementation(projects.features.swap.domain) + implementation(projects.features.swap.domain.api) + implementation(projects.features.swap.domain.models) /** Project - Core */ implementation(projects.core.decompose) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/AvailableSwapPairsComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/AvailableSwapPairsComponent.kt new file mode 100644 index 0000000000..93b1be67aa --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/AvailableSwapPairsComponent.kt @@ -0,0 +1,30 @@ +package com.tangem.features.onramp.swap.availablepairs + +import androidx.compose.runtime.Stable +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.StateFlow + +/** Token list component that present list of available tokens for swap */ +@Stable +internal interface AvailableSwapPairsComponent : ComposableContentComponent { + + /** Component factory */ + interface Factory : ComponentFactory + + /** + * Params + * + * @property userWalletId id of multi-currency wallet + * @property selectedStatus flow of selected status + * @property onTokenClick callback for token click + */ + data class Params( + val userWalletId: UserWalletId, + val selectedStatus: StateFlow, + val onTokenClick: (TokenItemState, CryptoCurrencyStatus) -> Unit, + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/DefaultAvailableSwapPairsComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/DefaultAvailableSwapPairsComponent.kt new file mode 100644 index 0000000000..fd77ee062f --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/DefaultAvailableSwapPairsComponent.kt @@ -0,0 +1,38 @@ +package com.tangem.features.onramp.swap.availablepairs + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +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.features.onramp.swap.availablepairs.model.AvailableSwapPairsModel +import com.tangem.features.onramp.tokenlist.ui.TokenList +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +@Stable +internal class DefaultAvailableSwapPairsComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted params: AvailableSwapPairsComponent.Params, +) : AvailableSwapPairsComponent, AppComponentContext by context { + + private val model: AvailableSwapPairsModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsStateWithLifecycle() + + TokenList(state = state, modifier = modifier) + } + + @AssistedFactory + interface Factory : AvailableSwapPairsComponent.Factory { + override fun create( + context: AppComponentContext, + params: AvailableSwapPairsComponent.Params, + ): DefaultAvailableSwapPairsComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsComponentModule.kt new file mode 100644 index 0000000000..0f451e0008 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.swap.availablepairs.di + +import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent +import com.tangem.features.onramp.swap.availablepairs.DefaultAvailableSwapPairsComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface AvailableSwapPairsComponentModule { + + @Binds + @Singleton + fun bindAvailableSwapPairsComponentFactory( + factory: DefaultAvailableSwapPairsComponent.Factory, + ): AvailableSwapPairsComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt new file mode 100644 index 0000000000..6bf7c67133 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.swap.availablepairs.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.swap.availablepairs.model.AvailableSwapPairsModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface AvailableSwapPairsModelModule { + + @Binds + @IntoMap + @ClassKey(AvailableSwapPairsModel::class) + fun bindAvailableSwapPairsModel(model: AvailableSwapPairsModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingTokenListItemConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingTokenListItemConverter.kt new file mode 100644 index 0000000000..056bbaf84f --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingTokenListItemConverter.kt @@ -0,0 +1,26 @@ +package com.tangem.features.onramp.swap.availablepairs.entity.converters + +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.utils.converter.Converter + +/** + * Convert from [CryptoCurrencyStatus] to loading [TokensListItemUM.Token] state + * +[REDACTED_AUTHOR] + */ +internal object LoadingTokenListItemConverter : Converter { + + override fun convert(value: CryptoCurrencyStatus): TokensListItemUM.Token { + return TokensListItemUM.Token( + state = TokenItemState.Loading( + id = value.currency.id.value, + iconState = CurrencyIconState.Loading, + titleState = TokenItemState.TitleState.Loading, + subtitleState = TokenItemState.SubtitleState.Loading, + ), + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetLoadingTokenItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetLoadingTokenItemsTransformer.kt new file mode 100644 index 0000000000..b14b3a023e --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetLoadingTokenItemsTransformer.kt @@ -0,0 +1,23 @@ +package com.tangem.features.onramp.swap.availablepairs.entity.transformers + +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.onramp.swap.availablepairs.entity.converters.LoadingTokenListItemConverter +import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer +import kotlinx.collections.immutable.toImmutableList + +/** + * Set [statuses] as loading items + * +[REDACTED_AUTHOR] + */ +internal class SetLoadingTokenItemsTransformer( + private val statuses: List, +) : TokenListUMTransformer { + + override fun transform(prevState: TokenListUM): TokenListUM { + return prevState.copy( + availableItems = LoadingTokenListItemConverter.convertList(input = statuses).toImmutableList(), + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt new file mode 100644 index 0000000000..b70a8135b2 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt @@ -0,0 +1,174 @@ +package com.tangem.features.onramp.swap.availablepairs.model + +import arrow.core.getOrElse +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.core.utils.getOrElse +import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.TokenList +import com.tangem.feature.swap.domain.GetAvailablePairsUseCase +import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo +import com.tangem.feature.swap.domain.models.domain.SwapPairLeast +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent +import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetLoadingTokenItemsTransformer +import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMController +import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer +import com.tangem.features.onramp.utils.SearchManager +import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer +import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@Suppress("LongParameterList") +internal class AvailableSwapPairsModel @Inject constructor( + paramsContainer: ParamsContainer, + getTokenListUseCase: GetTokenListUseCase, + override val dispatchers: CoroutineDispatcherProvider, + private val tokenListUMController: TokenListUMController, + private val searchManager: SearchManager, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, + private val getAvailablePairsUseCase: GetAvailablePairsUseCase, +) : Model() { + + val state: StateFlow = tokenListUMController.state + + private var params: AvailableSwapPairsComponent.Params = paramsContainer.require() + + private val tokenListFlow = getTokenListUseCase.launch(userWalletId = params.userWalletId) + .distinctUntilChanged() + .map { maybeTokenList -> + maybeTokenList.getOrElse( + ifLoading = { it ?: TokenList.Empty }, + ifError = { TokenList.Empty }, + ) + .flattenCurrencies() + } + .shareIn(scope = modelScope, started = SharingStarted.Eagerly, replay = 1) + + private val availablePairsByNetworkFlow = MutableStateFlow>>(emptyMap()) + + init { + subscribeOnUpdateState() + subscribeOnAvailablePairsUpdates() + } + + private fun subscribeOnUpdateState() { + combine( + flow = tokenListFlow, + flow2 = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(), + flow3 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(), + flow4 = searchManager.query, + flow5 = availablePairsByNetworkFlow + .map { it[params.selectedStatus.value?.toLeastTokenInfo()].orEmpty() } + .distinctUntilChanged(), + ) { currencies, appCurrency, isBalanceHidden, query, availablePairs -> + if (availablePairs.isEmpty()) { + SetLoadingTokenItemsTransformer(currencies) + } else { + val filterTokenList = currencies + .filterByQuery(query = query) + .filterByAvailability(availablePairs = availablePairs) + + Timber.e("${filterTokenList[true].orEmpty().size}") + + UpdateTokenItemsTransformer( + appCurrency = appCurrency, + onItemClick = params.onTokenClick, + statuses = filterTokenList, + isBalanceHidden = isBalanceHidden, + hasSearchBar = currencies.isNotEmpty(), + onQueryChange = ::onSearchQueryChange, + onActiveChange = ::onSearchBarActiveChange, + ) + } + } + .onEach(tokenListUMController::update) + .flowOn(dispatchers.main) + .launchIn(modelScope) + } + + private fun subscribeOnAvailablePairsUpdates() { + modelScope.launch { + params.selectedStatus + .filterNotNull() + .collectLatest { selectedStatus -> + val initialCurrency = selectedStatus.toLeastTokenInfo() + val tokenList = tokenListFlow.firstOrNull() ?: return@collectLatest + + val availablePairs = availablePairsByNetworkFlow.value[initialCurrency] + if (!availablePairs.isNullOrEmpty()) return@collectLatest + + val pairs = getAvailablePairsUseCase( + initialCurrency = initialCurrency, + currencies = tokenList.map(CryptoCurrencyStatus::currency), + ) + + Timber.e("$initialCurrency = ${pairs.size}") + + availablePairsByNetworkFlow.update { + it.toMutableMap().apply { + put(initialCurrency, pairs) + } + } + } + } + } + + private fun onSearchQueryChange(newQuery: String) { + val searchBar = tokenListUMController.getSearchBar() + if (searchBar?.searchBarUM?.query == newQuery) return + + modelScope.launch { + tokenListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery)) + + searchManager.update(newQuery) + } + } + + private fun onSearchBarActiveChange(isActive: Boolean) { + tokenListUMController.update( + transformer = UpdateSearchBarActiveStateTransformer( + isActive = isActive, + placeHolder = resourceReference(id = R.string.common_search), + ), + ) + } + + private fun List.filterByQuery(query: String): List { + return filter { + it.currency.name.contains(other = query, ignoreCase = true) || + it.currency.symbol.contains(other = query, ignoreCase = true) + } + } + + private fun List.filterByAvailability( + availablePairs: List, + ): Map> { + return groupBy { status -> + val isAvailable = availablePairs.map(SwapPairLeast::to).contains(status.toLeastTokenInfo()) + + isAvailable && + status.value !is CryptoCurrencyStatus.MissedDerivation && + status.value !is CryptoCurrencyStatus.Unreachable + } + } + + private fun CryptoCurrencyStatus.toLeastTokenInfo(): LeastTokenInfo { + return LeastTokenInfo( + contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0", + network = currency.network.backendId, + ) + } +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/GetAvailablePairsUseCase.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/GetAvailablePairsUseCase.kt new file mode 100644 index 0000000000..113c0ca915 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/GetAvailablePairsUseCase.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.swap.domain + +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.feature.swap.domain.api.SwapRepository +import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo +import com.tangem.feature.swap.domain.models.domain.SwapPairLeast + +class GetAvailablePairsUseCase( + private val swapRepository: SwapRepository, +) { + + suspend operator fun invoke( + initialCurrency: LeastTokenInfo, + currencies: List, + ): List { + return swapRepository.getPairsOnly(initialCurrency, currencies).pairs + } +} \ No newline at end of file From 71a9252de82a1cbdec9c74b406bf912d2632c9e3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 10 Nov 2024 16:41:05 +0400 Subject: [PATCH 22/33] Updated on 2026-08-14 --- .../ui/tokens/TokenItemStateConverter.kt | 5 +-- core/res/src/main/res/values-de/strings.xml | 6 ++++ core/res/src/main/res/values-es/strings.xml | 6 ++++ core/res/src/main/res/values-fr/strings.xml | 6 ++++ core/res/src/main/res/values-ja/strings.xml | 6 ++++ core/res/src/main/res/values-ru/strings.xml | 5 +++ .../src/main/res/values-uk-rUA/strings.xml | 6 ++++ core/res/src/main/res/values/strings.xml | 6 ++++ .../core/ui/components/token/TokenItem.kt | 34 +++++++++++-------- .../components/token/internal/TokenPrice.kt | 6 ++-- .../components/token/internal/TokenTitle.kt | 3 +- .../components/token/state/TokenItemState.kt | 5 +-- .../converters/ExchangeItemStateConverter.kt | 5 +-- .../ui/components/ExchangesBottomSheet.kt | 4 +-- .../impl/model/PortfolioTokenUMConverter.kt | 5 +-- .../preview/PreviewMyPortfolioUMProvider.kt | 7 ++-- .../model/AvailableSwapPairsModel.kt | 27 ++++++++++----- .../onramp/swap/entity/ExchangeCardUM.kt | 16 ++++++--- .../swap/entity/SwapSelectTokensController.kt | 8 ++--- .../RemoveSelectedFromTokenTransformer.kt | 7 ++-- .../swap/entity/utils/ExchangeCardUMExt.kt | 18 ++++++++++ .../features/onramp/swap/ui/ExchangeCard.kt | 8 +++-- .../UpdateTokenItemsTransformer.kt | 21 ++++++++---- .../tokenlist/model/OnrampTokenListModel.kt | 14 +++++++- .../ui/preview/PreviewTokenListUMProvider.kt | 13 +++++-- .../presentation/common/WalletPreviewData.kt | 5 +-- .../common/preview/WalletScreenPreviewData.kt | 8 ++--- .../CryptoCurrencyToDraggableItemConverter.kt | 3 +- 28 files changed, 193 insertions(+), 70 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index a4e5324a74..62e5cb7ed4 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -5,6 +5,7 @@ import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIco import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.percent @@ -126,7 +127,7 @@ class TokenItemStateConverter( is CryptoCurrencyStatus.Unreachable, is CryptoCurrencyStatus.NoAmount, -> { - TokenItemState.TitleState.Content(text = currencyStatus.currency.name) + TokenItemState.TitleState.Content(text = stringReference(currencyStatus.currency.name)) } is CryptoCurrencyStatus.Loaded, is CryptoCurrencyStatus.Custom, @@ -134,7 +135,7 @@ class TokenItemStateConverter( is CryptoCurrencyStatus.NoAccount, -> { TokenItemState.TitleState.Content( - text = currencyStatus.currency.name, + text = stringReference(currencyStatus.currency.name), hasPending = value.hasCurrentNetworkTransactions, ) } diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 198db71cec..9c2e4d268b 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1,5 +1,8 @@ + Wähle den Token + Du möchtest erhalten + Du möchtest tauschen Netzwerk wählen Token anlegen Token verwalten @@ -852,6 +855,9 @@ Tausche diesen Token gegen einen anderen zu %1$s Servicegebühren von Februar %2$s-%3$s. Tausche mit Changelly, %s Gebühren Jetzt tauschen + Nicht verfügbar zum Kauf + Nicht zum Verkauf verfügbar + Nicht verfügbar für Tausch von %s Vertrag: %s Du hast noch keine Transaktionen Der Transaktionsverlauf konnte nicht geladen werden.\nKlicke auf die Schaltfläche Neu laden, um die Informationen zu aktualisieren. diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 3dc04b55bb..028dbab45b 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -1,5 +1,8 @@ + Elige el token + Desea recibir + Quiere intercambiar Elige red Agregue un token personalizado Gestionar tokens @@ -847,6 +850,9 @@ Cambie este token por otro por una tarifa de servicio de %1$s del %2$s al %3$s de febrero. Intercambio con Changelly, %s tarifa Intercambie ahora + No disponible para compra + No disponible para vender + No disponible para cambio desde %s contacto: %s Aún no tiene ninguna transacción Error al cargar el historial de transacciones.\nHaga clic en el botón de recarga para actualizar la información. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 2f534dd458..dbd1f5f831 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1,5 +1,8 @@ + Choisissez le jeton + Vous souhaitez recevoir + Vous souhaitez échanger Choisissez le réseau Ajouter un jeton personnalisé Gérer les jetons @@ -847,6 +850,9 @@ Échangez ce jeton contre un autre moyennant des frais de service de %1$s du %2$s au %3$s février. Échangez avec Changelly, %s frais Échangez maintenant + Non disponible à l\'achat + Indisponible à la vente + Non disponible pour l\'échange de %s contrat : %s Vous n\'avez pas encore de transactions Échec du chargement de l\'historique des transactions.\nCliquez sur le bouton de rechargement pour mettre à jour les informations. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index d64d288ba7..4b823654c1 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -1,5 +1,8 @@ + トークンを選択 + 受け取りたい + 交換したい ネットワークを選択 カスタムトークンの追加 トークンの管理 @@ -842,6 +845,9 @@ このトークンは、2月%2$s-%3$s の間、%1$s のサービス手数料で別のトークンと交換できます。 Changellyでスワップ、手数料%s 今すぐスワップ + 買付できません + 売却できません + %sからのスワップは利用できません コントラクト: %s まだ取引はありません 取引履歴の読み込みに失敗しました。\n情報を更新するには、リロードボタンをクリックしてください。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 50fe9580e5..9d3df7e413 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,5 +1,8 @@ + Выберите токен + Вы хотите получить + Вы хотите обменять Выберите сеть Добавить токен Валюты @@ -867,6 +870,8 @@ Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. Обмен с Changelly, %s комиссии Обменять + Недоступно для покупки + Недоступно для продажи контракт: %s У вас еще нет транзакций Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 18f9697aaa..222dbdbe02 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -1,5 +1,8 @@ + Оберіть токен + Ви хочете отримати + Ви хочете обміняти Оберіть мережу Додати токен Токени @@ -866,6 +869,9 @@ Обмінюйте цей токен на інші з %1$s комісією за обслуговування з %2$s по %3$s лютого. Обмін із Changelly, %s комісії Обміняти + Недоступно для покупки + Недоступно для продажу + Недоступно для обміну з %s контракт: %s У вас ще немає транзакцій Не вдалося завантажити історію транзакцій.\nНатисніть кнопку нижче, щоб оновити інформацію diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 1e4d43cba5..1047741f9a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,5 +1,8 @@ + Choose the Token + You want to Receive + You want to Swap Choose network Add custom token Manage tokens @@ -860,6 +863,9 @@ Exchange this token for another at %1$s service fees from February %2$s-%3$s. Swap with Changelly, %s fees Swap now + Unavailable to purchase + Unavailable to sell + Unavailable for swap from %s contract: %s You don\'t have any transactions yet Failed to load transaction history.\nClick on reload button to update the information. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt index 33f3d649fc..c504817b1b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt @@ -25,6 +25,7 @@ import com.tangem.core.ui.components.token.internal.* import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.rememberHapticFeedback +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -458,7 +459,7 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider { - PriceText(text = state.value, modifier = modifier, isAvailable = state.isAvailable) + PriceText(text = state.value.resolveReference(), modifier = modifier, isAvailable = state.isAvailable) } is TokenPriceState.Unknown -> { PriceText(text = DASH_SIGN, modifier = modifier) @@ -158,7 +160,7 @@ private class TokenPriceChangeStateProvider : CollectionPreviewParameterProvider priceChangePercent = "2.5%", type = PriceChangeType.NEUTRAL, ), - TokenPriceState.TextContent(value = "Subtitle", isAvailable = true), + TokenPriceState.TextContent(value = stringReference(value = "Subtitle"), isAvailable = true), TokenPriceState.Unknown, TokenPriceState.Loading, TokenPriceState.Locked, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt index 9bf3412f33..7f379e09a6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import com.tangem.core.ui.R import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.components.token.state.TokenItemState.TitleState as TokenTitleState @@ -46,7 +47,7 @@ private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Mo * So we need to use [weight] to avoid displacement. */ CurrencyNameText( - name = state.text, + name = state.text.resolveReference(), isAvailable = state.isAvailable, modifier = Modifier.weight(weight = 1f, fill = false), ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt index e16a77a33b..6f3b2342a9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.audits.AuditLabelUM import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.marketprice.PriceChangeType +import com.tangem.core.ui.extensions.TextReference /** TokenItem component state */ @Immutable @@ -161,7 +162,7 @@ sealed class TokenItemState { sealed class TitleState { data class Content( - val text: String, + val text: TextReference, val hasPending: Boolean = false, val isAvailable: Boolean = true, ) : TitleState() @@ -180,7 +181,7 @@ sealed class TokenItemState { val type: PriceChangeType, ) : SubtitleState() - data class TextContent(val value: String, val isAvailable: Boolean = true) : SubtitleState() + data class TextContent(val value: TextReference, val isAvailable: Boolean = true) : SubtitleState() data object Unknown : SubtitleState() diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/ExchangeItemStateConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/ExchangeItemStateConverter.kt index f6f4d3ba8a..0ddafb4e83 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/ExchangeItemStateConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/ExchangeItemStateConverter.kt @@ -4,6 +4,7 @@ import com.tangem.core.ui.components.audits.AuditLabelUM import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.markets.TokenMarketExchange import com.tangem.domain.markets.TokenMarketExchange.TrustScore @@ -26,7 +27,7 @@ internal object ExchangeItemStateConverter : Converter onTokenItemClick(status) }, ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt index b2728ffc33..79177f24a4 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt @@ -4,6 +4,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioTokenUM @@ -49,10 +50,12 @@ internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider + ) { currencies, appCurrencyAndBalanceHiding, selectedStatus, query, availablePairs -> if (availablePairs.isEmpty()) { SetLoadingTokenItemsTransformer(currencies) } else { + val (appCurrency, isBalanceHidden) = appCurrencyAndBalanceHiding + val filterTokenList = currencies .filterByQuery(query = query) .filterByAvailability(availablePairs = availablePairs) - Timber.e("${filterTokenList[true].orEmpty().size}") - UpdateTokenItemsTransformer( appCurrency = appCurrency, onItemClick = params.onTokenClick, statuses = filterTokenList, isBalanceHidden = isBalanceHidden, hasSearchBar = currencies.isNotEmpty(), + unavailableTokensHeaderReference = resourceReference( + id = R.string.tokens_list_unavailable_to_swap_header, + wrappedList(selectedStatus?.currency?.name?.capitalize() ?: ""), + ), onQueryChange = ::onSearchQueryChange, onActiveChange = ::onSearchBarActiveChange, ) @@ -115,8 +120,6 @@ internal class AvailableSwapPairsModel @Inject constructor( currencies = tokenList.map(CryptoCurrencyStatus::currency), ) - Timber.e("$initialCurrency = ${pairs.size}") - availablePairsByNetworkFlow.update { it.toMutableMap().apply { put(initialCurrency, pairs) @@ -126,6 +129,14 @@ internal class AvailableSwapPairsModel @Inject constructor( } } + private fun getAppCurrencyAndBalanceHidingFlow(): Flow> { + return combine( + flow = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(), + flow2 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(), + transform = ::Pair, + ) + } + private fun onSearchQueryChange(newQuery: String) { val searchBar = tokenListUMController.getSearchBar() if (searchBar?.searchBarUM?.query == newQuery) return diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt index e427825e21..11486011da 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt @@ -3,6 +3,7 @@ package com.tangem.features.onramp.swap.entity import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onramp.impl.R /** @@ -24,18 +25,23 @@ internal sealed interface ExchangeCardUM { /** * Empty state * - * @property titleReference title reference + * @property titleReference title reference + * @property subtitleReference empty token subtitle reference */ - data class Empty(override val titleReference: TextReference) : ExchangeCardUM { + data class Empty( + override val titleReference: TextReference, + val subtitleReference: TextReference, + ) : ExchangeCardUM { override val removeButtonUM: RemoveButtonUM? = null - // TODO: [REDACTED_JIRA] override val tokenItemState: TokenItemState = TokenItemState.Content( id = "empty", iconState = CurrencyIconState.Empty(R.drawable.ic_empty_64), - titleState = TokenItemState.TitleState.Content(text = "Choose the Token"), - subtitleState = TokenItemState.SubtitleState.TextContent(value = "You want to Swap"), + titleState = TokenItemState.TitleState.Content( + text = resourceReference(id = R.string.action_buttons_swap_choose_token), + ), + subtitleState = TokenItemState.SubtitleState.TextContent(value = subtitleReference), fiatAmountState = TokenItemState.FiatAmountState.Content(text = ""), subtitle2State = TokenItemState.Subtitle2State.TextContent(text = ""), onItemClick = null, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt index 64fac02313..f6cd655930 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt @@ -1,8 +1,8 @@ package com.tangem.features.onramp.swap.entity import com.tangem.core.decompose.navigation.Router -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeFrom +import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeTo import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -25,8 +25,8 @@ internal class SwapSelectTokensController @Inject constructor( private val _state: MutableStateFlow = MutableStateFlow( value = SwapSelectTokensUM( onBackClick = router::pop, - exchangeFrom = ExchangeCardUM.Empty(titleReference = resourceReference(id = R.string.swapping_from_title)), - exchangeTo = ExchangeCardUM.Empty(titleReference = resourceReference(id = R.string.swapping_to_title)), + exchangeFrom = createEmptyExchangeFrom(), + exchangeTo = createEmptyExchangeTo(), ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt index 52d581ddf2..828f9d6ad0 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt @@ -1,8 +1,9 @@ package com.tangem.features.onramp.swap.entity.transformer -import com.tangem.features.onramp.swap.entity.ExchangeCardUM import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer +import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeFrom +import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeTo /** * Transformer for removing selected "from" token @@ -13,8 +14,8 @@ internal object RemoveSelectedFromTokenTransformer : SwapSelectTokensUMTransform override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM { return prevState.copy( - exchangeFrom = ExchangeCardUM.Empty(titleReference = prevState.exchangeFrom.titleReference), - exchangeTo = ExchangeCardUM.Empty(titleReference = prevState.exchangeTo.titleReference), + exchangeFrom = createEmptyExchangeFrom(), + exchangeTo = createEmptyExchangeTo(), ) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt index 5e6a03ea40..6daf403437 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt @@ -1,8 +1,26 @@ package com.tangem.features.onramp.swap.entity.utils import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.swap.entity.ExchangeCardUM +/** Create empty exchange "from" card */ +internal fun createEmptyExchangeFrom(): ExchangeCardUM.Empty { + return ExchangeCardUM.Empty( + titleReference = resourceReference(id = R.string.swapping_from_title), + subtitleReference = resourceReference(id = R.string.action_buttons_you_want_to_swap), + ) +} + +/** Create empty exchange "to" card */ +internal fun createEmptyExchangeTo(): ExchangeCardUM.Empty { + return ExchangeCardUM.Empty( + titleReference = resourceReference(id = R.string.swapping_to_title), + subtitleReference = resourceReference(id = R.string.action_buttons_you_want_to_receive), + ) +} + /** * Convert from [ExchangeCardUM] to [ExchangeCardUM.Filled] * diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt index 92a3229445..35ad87452f 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt @@ -26,6 +26,7 @@ import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.onramp.impl.R @@ -112,7 +113,10 @@ private fun Preview_ExchangeCard(@PreviewParameter(ExchangeCardUMProvider::class private class ExchangeCardUMProvider : PreviewParameterProvider { override val values: Sequence = sequenceOf( - ExchangeCardUM.Empty(titleReference = resourceReference(id = R.string.swapping_from_title)), + ExchangeCardUM.Empty( + titleReference = resourceReference(id = R.string.swapping_from_title), + subtitleReference = resourceReference(id = R.string.action_buttons_you_want_to_swap), + ), createFilled(removeButtonUM = null), createFilled(removeButtonUM = ExchangeCardUM.RemoveButtonUM { }), ) @@ -124,7 +128,7 @@ private class ExchangeCardUMProvider : PreviewParameterProvider tokenItemState = TokenItemState.Content( id = "1", iconState = CurrencyIconState.Locked, - titleState = TokenItemState.TitleState.Content(text = "Bitcoin"), + titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Bitcoin")), fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"), subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "0,35853044 BTC"), subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt index a4eed528a1..ee219d1fc5 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -23,6 +23,7 @@ internal class UpdateTokenItemsTransformer( private val statuses: Map>, private val isBalanceHidden: Boolean, private val hasSearchBar: Boolean, + private val unavailableTokensHeaderReference: TextReference, private val onQueryChange: (String) -> Unit, private val onActiveChange: (Boolean) -> Unit, ) : TokenListUMTransformer { @@ -62,11 +63,7 @@ internal class UpdateTokenItemsTransformer( .toImmutableList(), unavailableItems = buildList { if (unavailableItems.isNotEmpty()) { - // TODO: [REDACTED_JIRA] - createGroupTitle( - textReference = stringReference(value = "Unavailable tokens"), - ) - .let(::add) + createGroupTitle(textReference = unavailableTokensHeaderReference).let(::add) } addAll(unavailableItems) @@ -91,11 +88,21 @@ internal class UpdateTokenItemsTransformer( return TokenItemStateConverter( appCurrency = appCurrency, iconStateProvider = { CryptoCurrencyToIconStateConverter(isAvailable = false).convert(it) }, - titleStateProvider = { TokenItemState.TitleState.Content(text = it.currency.name, isAvailable = false) }, + titleStateProvider = { + TokenItemState.TitleState.Content( + text = stringReference(value = it.currency.name), + isAvailable = false, + ) + }, subtitleStateProvider = { when (it.value) { CryptoCurrencyStatus.Loading -> TokenItemState.SubtitleState.Loading - else -> TokenItemState.SubtitleState.TextContent(value = it.currency.symbol, isAvailable = false) + else -> { + TokenItemState.SubtitleState.TextContent( + value = stringReference(value = it.currency.symbol), + isAvailable = false, + ) + } } }, fiatAmountStateProvider = { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 99f147b1c3..ec32132b9d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -4,6 +4,7 @@ import arrow.core.getOrElse import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -12,9 +13,9 @@ import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList -import com.tangem.features.onramp.impl.R import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.entity.OnrampOperation +import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.entity.TokenListUMController @@ -74,6 +75,17 @@ internal class OnrampTokenListModel @Inject constructor( statuses = filterTokenList, isBalanceHidden = isBalanceHidden, hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(), + unavailableTokensHeaderReference = when (params.filterOperation) { + OnrampOperation.BUY -> resourceReference(id = R.string.tokens_list_unavailable_to_purchase_header) + OnrampOperation.SELL -> resourceReference(id = R.string.tokens_list_unavailable_to_sell_header) + OnrampOperation.SWAP -> { + // TODO: [REDACTED_JIRA] + resourceReference( + id = R.string.tokens_list_unavailable_to_swap_header, + wrappedList(""), + ) + } + }, onQueryChange = ::onSearchQueryChange, onActiveChange = ::onSearchBarActiveChange, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt index 273cad4fd0..501874e719 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt @@ -7,6 +7,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.tokenlist.entity.TokenListUM import kotlinx.collections.immutable.persistentListOf @@ -69,7 +70,7 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider Date: Sun, 10 Nov 2024 19:01:52 +0500 Subject: [PATCH 23/33] Updated on 2026-08-14 --- .../onramp/{di => }/OnrampFeatureModule.kt | 4 +-- .../ConfirmResidencyComponent.kt | 2 +- .../DefaultConfirmResidencyComponent.kt | 9 +++---- .../PreviewConfirmResidencyComponent.kt | 9 +++---- .../OnrampConfirmResidencyComponentModule.kt | 20 ++++++++++++++ .../di/OnrampConfirmResidencyModelModule.kt | 19 ++++++++++++++ .../entity/ConfirmResidencyUM.kt | 2 +- .../model/ConfirmResidencyModel.kt | 6 ++--- .../ui/ConfirmResidencyBottomSheet.kt | 8 +++--- .../onramp/di/OnrampComponentModule.kt | 26 ------------------- .../impl => root}/DefaultOnrampComponent.kt | 4 +-- .../onramp/root/di/OnrampComponentModule.kt | 18 +++++++++++++ .../onramp/{ => root}/entity/OnrampChild.kt | 2 +- .../DefaultOnrampOperationComponent.kt | 2 +- .../swap/DefaultSwapSelectTokensComponent.kt | 2 +- .../tokenlist/OnrampTokenListComponent.kt | 2 +- .../{ => tokenlist}/entity/OnrampOperation.kt | 2 +- .../tokenlist/model/OnrampTokenListModel.kt | 2 +- 18 files changed, 83 insertions(+), 56 deletions(-) rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{di => }/OnrampFeatureModule.kt (76%) rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{component => confirmresidency}/ConfirmResidencyComponent.kt (89%) rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{component/impl => confirmresidency}/DefaultConfirmResidencyComponent.kt (90%) rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{component/preview => confirmresidency}/PreviewConfirmResidencyComponent.kt (84%) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyComponentModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyModelModule.kt rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{ => confirmresidency}/entity/ConfirmResidencyUM.kt (86%) rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{ => confirmresidency}/model/ConfirmResidencyModel.kt (90%) rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{ => confirmresidency}/ui/ConfirmResidencyBottomSheet.kt (95%) delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{component/impl => root}/DefaultOnrampComponent.kt (96%) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/di/OnrampComponentModule.kt rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{ => root}/entity/OnrampChild.kt (81%) rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{ => tokenlist}/entity/OnrampOperation.kt (70%) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampFeatureModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/OnrampFeatureModule.kt similarity index 76% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampFeatureModule.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/OnrampFeatureModule.kt index cac4df29b9..6e668e4f69 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampFeatureModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/OnrampFeatureModule.kt @@ -1,8 +1,6 @@ -package com.tangem.features.onramp.di +package com.tangem.features.onramp import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.features.onramp.DefaultOnrampFeatureToggles -import com.tangem.features.onramp.OnrampFeatureToggles import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/ConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt similarity index 89% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/ConfirmResidencyComponent.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt index 74742aa575..bae3a5be6e 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/ConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.component +package com.tangem.features.onramp.confirmresidency import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableBottomSheetComponent diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt similarity index 90% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt index 9d3406858d..5d6b10d508 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.component.impl +package com.tangem.features.onramp.confirmresidency import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -14,12 +14,11 @@ 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.decompose.ComposableBottomSheetComponent -import com.tangem.features.onramp.component.ConfirmResidencyComponent -import com.tangem.features.onramp.model.ConfirmResidencyModel +import com.tangem.features.onramp.confirmresidency.model.ConfirmResidencyModel import com.tangem.features.onramp.selectcountry.SelectCountryComponent import com.tangem.features.onramp.selectcountry.entity.ConfirmResidencyBottomSheetConfig -import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheet -import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheetContent +import com.tangem.features.onramp.confirmresidency.ui.ConfirmResidencyBottomSheet +import com.tangem.features.onramp.confirmresidency.ui.ConfirmResidencyBottomSheetContent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/PreviewConfirmResidencyComponent.kt similarity index 84% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/PreviewConfirmResidencyComponent.kt index bb051a618e..adc039dca9 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/PreviewConfirmResidencyComponent.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.component.preview +package com.tangem.features.onramp.confirmresidency import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -7,10 +7,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.extensions.stringReference -import com.tangem.features.onramp.component.ConfirmResidencyComponent -import com.tangem.features.onramp.entity.ConfirmResidencyUM -import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheet -import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheetContent +import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM +import com.tangem.features.onramp.confirmresidency.ui.ConfirmResidencyBottomSheet +import com.tangem.features.onramp.confirmresidency.ui.ConfirmResidencyBottomSheetContent import kotlinx.coroutines.flow.MutableStateFlow internal class PreviewConfirmResidencyComponent( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyComponentModule.kt new file mode 100644 index 0000000000..7b97f25196 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.confirmresidency.di + +import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent +import com.tangem.features.onramp.confirmresidency.DefaultConfirmResidencyComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampConfirmResidencyComponentModule { + + @Binds + @Singleton + fun bindConfirmResidencyComponentFactory( + factory: DefaultConfirmResidencyComponent.Factory, + ): ConfirmResidencyComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyModelModule.kt new file mode 100644 index 0000000000..ab79a46e1c --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/di/OnrampConfirmResidencyModelModule.kt @@ -0,0 +1,19 @@ +package com.tangem.features.onramp.confirmresidency.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.confirmresidency.model.ConfirmResidencyModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampConfirmResidencyModelModule { + @Binds + @IntoMap + @ClassKey(ConfirmResidencyModel::class) + fun bindConfirmResidencyModel(model: ConfirmResidencyModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/entity/ConfirmResidencyUM.kt similarity index 86% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyUM.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/entity/ConfirmResidencyUM.kt index 106df7e1bc..364743a457 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/entity/ConfirmResidencyUM.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.entity +package com.tangem.features.onramp.confirmresidency.entity import com.tangem.core.ui.extensions.TextReference diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt similarity index 90% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt index b4f0283566..bedc4c6094 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.model +package com.tangem.features.onramp.confirmresidency.model import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate @@ -6,8 +6,8 @@ import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.extensions.resourceReference -import com.tangem.features.onramp.component.ConfirmResidencyComponent -import com.tangem.features.onramp.entity.ConfirmResidencyUM +import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent +import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selectcountry.entity.ConfirmResidencyBottomSheetConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ConfirmResidencyBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ui/ConfirmResidencyBottomSheet.kt similarity index 95% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ConfirmResidencyBottomSheet.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ui/ConfirmResidencyBottomSheet.kt index a950da23ab..cb95070456 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ConfirmResidencyBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ui/ConfirmResidencyBottomSheet.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.ui +package com.tangem.features.onramp.confirmresidency.ui import android.content.res.Configuration import androidx.compose.foundation.layout.* @@ -27,9 +27,9 @@ 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.features.onramp.component.ConfirmResidencyComponent -import com.tangem.features.onramp.component.preview.PreviewConfirmResidencyComponent -import com.tangem.features.onramp.entity.ConfirmResidencyUM +import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent +import com.tangem.features.onramp.confirmresidency.PreviewConfirmResidencyComponent +import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM import com.tangem.features.onramp.impl.R @Composable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt deleted file mode 100644 index 6f27d391db..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.features.onramp.di - -import com.tangem.features.onramp.component.ConfirmResidencyComponent -import com.tangem.features.onramp.component.OnrampComponent -import com.tangem.features.onramp.component.impl.DefaultConfirmResidencyComponent -import com.tangem.features.onramp.component.impl.DefaultOnrampComponent -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface OnrampComponentModule { - - @Binds - @Singleton - fun bindOnrampComponentFactory(factory: DefaultOnrampComponent.Factory): OnrampComponent.Factory - - @Binds - @Singleton - fun bindConfirmResidencyComponentFactory( - factory: DefaultConfirmResidencyComponent.Factory, - ): ConfirmResidencyComponent.Factory -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt similarity index 96% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt index 1957c1def4..c65a434c7a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.component.impl +package com.tangem.features.onramp.root import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -13,7 +13,7 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.onramp.component.OnrampComponent -import com.tangem.features.onramp.entity.OnrampChild +import com.tangem.features.onramp.root.entity.OnrampChild import com.tangem.features.onramp.settings.OnrampSettingsComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/di/OnrampComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/di/OnrampComponentModule.kt new file mode 100644 index 0000000000..9a1601386a --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/di/OnrampComponentModule.kt @@ -0,0 +1,18 @@ +package com.tangem.features.onramp.root.di + +import com.tangem.features.onramp.component.OnrampComponent +import com.tangem.features.onramp.root.DefaultOnrampComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampComponentModule { + + @Binds + @Singleton + fun bindOnrampComponentFactory(factory: DefaultOnrampComponent.Factory): OnrampComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampChild.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/entity/OnrampChild.kt similarity index 81% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampChild.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/entity/OnrampChild.kt index e18f168bff..994a986713 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampChild.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/entity/OnrampChild.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.entity +package com.tangem.features.onramp.root.entity import kotlinx.serialization.Serializable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt index da3151dadd..e144bfbffe 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt @@ -11,7 +11,7 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.usecase.GetWalletsUseCase -import com.tangem.features.onramp.entity.OnrampOperation +import com.tangem.features.onramp.tokenlist.entity.OnrampOperation import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selecttoken.ui.OnrampSelectToken import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt index fdcecebdc0..fff44f7b73 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt @@ -8,7 +8,7 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.features.onramp.component.SwapSelectTokensComponent -import com.tangem.features.onramp.entity.OnrampOperation +import com.tangem.features.onramp.tokenlist.entity.OnrampOperation import com.tangem.features.onramp.swap.model.SwapSelectTokensModel import com.tangem.features.onramp.swap.ui.SwapSelectTokens import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt index 6cea4a5b28..d85af84cf3 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt @@ -6,7 +6,7 @@ import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.features.onramp.entity.OnrampOperation +import com.tangem.features.onramp.tokenlist.entity.OnrampOperation /** Token list component that present list of token for multi-currency wallet */ @Stable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/OnrampOperation.kt similarity index 70% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/OnrampOperation.kt index 54cfa7afa4..dea01060e5 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/OnrampOperation.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.entity +package com.tangem.features.onramp.tokenlist.entity /** * Onramp operation diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index ec32132b9d..ffda60ba6a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -14,8 +14,8 @@ import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.usecase.GetWalletsUseCase -import com.tangem.features.onramp.entity.OnrampOperation import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.tokenlist.entity.OnrampOperation import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.entity.TokenListUMController From a950ca16b7abac19d3298f60ec65174a3fefe98f Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 10 Nov 2024 18:25:23 +0400 Subject: [PATCH 24/33] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 11 ++++++++- .../com/tangem/common/routing/AppRoute.kt | 5 ++++ .../swap/DefaultSwapServiceLoader.kt | 20 ++++++++++++---- features/onramp/impl/build.gradle.kts | 1 + .../swap/DefaultSwapSelectTokensComponent.kt | 24 ++++++++++--------- .../swap/model/SwapSelectTokensModel.kt | 13 ++++++---- .../onramp/swap/ui/SwapSelectTokens.kt | 3 ++- .../WalletCurrencyActionsClickIntents.kt | 2 +- 8 files changed, 55 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index fe71e3ac0d..e53226e658 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -11,10 +11,11 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent -import com.tangem.features.onramp.component.BuyCryptoComponent import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent +import com.tangem.features.onramp.component.BuyCryptoComponent import com.tangem.features.onramp.component.OnrampComponent import com.tangem.features.onramp.component.SellCryptoComponent +import com.tangem.features.onramp.component.SwapSelectTokensComponent import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter @@ -53,6 +54,7 @@ internal class ChildFactory @Inject constructor( private val onrampComponentFactory: OnrampComponent.Factory, private val buyCryptoComponentFactory: BuyCryptoComponent.Factory, private val sellCryptoComponentFactory: SellCryptoComponent.Factory, + private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory, private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory, private val sendRouter: SendRouter, private val tokenDetailsRouter: TokenDetailsRouter, @@ -215,6 +217,13 @@ internal class ChildFactory @Inject constructor( componentFactory = sellCryptoComponentFactory, ) } + is AppRoute.SwapCrypto -> { + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = SwapSelectTokensComponent.Params(userWalletId = route.userWalletId), + componentFactory = swapSelectTokensComponentFactory, + ) + } is AppRoute.Onboarding -> { route.asComponentChild( contextProvider = contextProvider(route, contextFactory), diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index f5982495e5..d69c5ed969 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -299,6 +299,11 @@ sealed class AppRoute(val path: String) : Route { val userWalletId: UserWalletId, ) : AppRoute(path = "/sell_crypto/${userWalletId.stringValue}") + @Serializable + data class SwapCrypto( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/swap_crypto/${userWalletId.stringValue}") + // Onboarding V2 data class Onboarding( val scanResponse: ScanResponse, diff --git a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt index 20044793fe..90a267cc3d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/exchangeservice/swap/DefaultSwapServiceLoader.kt @@ -36,7 +36,8 @@ internal class DefaultSwapServiceLoader @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, ) : SwapServiceLoader { - private val initializationStatuses: Map = mapOf() + private val initializationStatuses = + MutableStateFlow>(value = emptyMap()) override suspend fun update(userWalletId: UserWalletId, userTokens: UserTokensResponse) { withContext(dispatchers.io) { @@ -73,9 +74,18 @@ internal class DefaultSwapServiceLoader @Inject constructor( } private fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow { - return initializationStatuses.getOrDefault( - key = userWalletId, - defaultValue = MutableStateFlow(value = lceLoading()), - ) + val initializationStatus = initializationStatuses.value.get(key = userWalletId) + + if (initializationStatus != null) return initializationStatus + + val default: InitializationStatusFlow = MutableStateFlow(value = lceLoading()) + + initializationStatuses.update { + it.toMutableMap().apply { + put(key = userWalletId, value = default) + } + } + + return default } } \ No newline at end of file diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 0aafc9b959..314c0f9529 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(projects.core.featuretoggles) /** Project - Common */ + implementation(projects.common.routing) implementation(projects.common.ui) /** Project - Domain */ diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt index fff44f7b73..457590ea6b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt @@ -8,10 +8,11 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.features.onramp.component.SwapSelectTokensComponent -import com.tangem.features.onramp.tokenlist.entity.OnrampOperation +import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent import com.tangem.features.onramp.swap.model.SwapSelectTokensModel import com.tangem.features.onramp.swap.ui.SwapSelectTokens import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent +import com.tangem.features.onramp.tokenlist.entity.OnrampOperation import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -19,11 +20,12 @@ import dagger.assisted.AssistedInject @Stable internal class DefaultSwapSelectTokensComponent @AssistedInject constructor( tokenListComponentFactory: OnrampTokenListComponent.Factory, + availableSwapPairsComponentFactory: AvailableSwapPairsComponent.Factory, @Assisted private val appComponentContext: AppComponentContext, @Assisted private val params: SwapSelectTokensComponent.Params, ) : AppComponentContext by appComponentContext, SwapSelectTokensComponent { - private val model: SwapSelectTokensModel = getOrCreateModel() + private val model: SwapSelectTokensModel = getOrCreateModel(params) private val selectFromTokenListComponent: OnrampTokenListComponent = tokenListComponentFactory.create( context = child(key = "select_from_token_list"), @@ -35,15 +37,15 @@ internal class DefaultSwapSelectTokensComponent @AssistedInject constructor( ), ) - private val selectToTokenListComponent: OnrampTokenListComponent = tokenListComponentFactory.create( - context = child(key = "select_to_token_list"), - params = OnrampTokenListComponent.Params( - filterOperation = OnrampOperation.SWAP, - hasSearchBar = true, - userWalletId = params.userWalletId, - onTokenClick = model::selectToToken, - ), - ) + private val selectToTokenListComponent: AvailableSwapPairsComponent = + availableSwapPairsComponentFactory.create( + context = child(key = "select_to_token_list"), + params = AvailableSwapPairsComponent.Params( + userWalletId = params.userWalletId, + selectedStatus = model.fromCurrencyStatus, + onTokenClick = model::selectToToken, + ), + ) @Composable override fun Content(modifier: Modifier) { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt index 858f97a8c0..a5208ac04a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt @@ -9,6 +9,7 @@ import com.tangem.features.onramp.swap.entity.transformer.RemoveSelectedFromToke import com.tangem.features.onramp.swap.entity.transformer.SelectFromTokenTransformer import com.tangem.features.onramp.swap.entity.transformer.SelectToTokenTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -20,8 +21,10 @@ internal class SwapSelectTokensModel @Inject constructor( val state: StateFlow = controller.state - private var fromCurrencyStatus: CryptoCurrencyStatus? = null - private var toCurrencyStatus: CryptoCurrencyStatus? = null + val fromCurrencyStatus: StateFlow get() = _fromCurrencyStatus + + private val _fromCurrencyStatus = MutableStateFlow(value = null) + private val _toCurrencyStatus = MutableStateFlow(value = null) /** * Select "from" token @@ -30,7 +33,7 @@ internal class SwapSelectTokensModel @Inject constructor( * @param status crypto currency status */ fun selectFromToken(selectedTokenItemState: TokenItemState, status: CryptoCurrencyStatus) { - fromCurrencyStatus = status + _fromCurrencyStatus.value = status controller.update( transformer = SelectFromTokenTransformer( @@ -47,7 +50,7 @@ internal class SwapSelectTokensModel @Inject constructor( * @param status crypto currency status */ fun selectToToken(selectedTokenItemState: TokenItemState, status: CryptoCurrencyStatus) { - toCurrencyStatus = status + _toCurrencyStatus.value = status controller.update(transformer = SelectToTokenTransformer(selectedTokenItemState)) @@ -55,7 +58,7 @@ internal class SwapSelectTokensModel @Inject constructor( } private fun onRemoveClick() { - fromCurrencyStatus = null + _fromCurrencyStatus.value = null controller.update(transformer = RemoveSelectedFromTokenTransformer) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt index 3179099e4d..b2c0e550bd 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent import com.tangem.features.onramp.swap.entity.ExchangeCardUM import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent @@ -34,7 +35,7 @@ import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent internal fun SwapSelectTokens( state: SwapSelectTokensUM, selectFromTokenListComponent: OnrampTokenListComponent, - selectToTokenListComponent: OnrampTokenListComponent, + selectToTokenListComponent: AvailableSwapPairsComponent, modifier: Modifier = Modifier, ) { BackHandler(onBack = state.onBackClick) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index dba1cd4f91..1a4283bbd6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -445,7 +445,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( override fun onMultiWalletSwapClick(userWalletId: UserWalletId) { // TODO [REDACTED_JIRA] - // appRouter.push(route = AppRoute.SwapCrypto(userWalletId = userWalletId)) + appRouter.push(route = AppRoute.SwapCrypto(userWalletId = userWalletId)) } override fun onMultiWalletBuyClick(userWalletId: UserWalletId) { From 3b4d1ad6f6ee63b4fe2618041392b7dba4241b23 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 13:33:40 +0500 Subject: [PATCH 25/33] Updated on 2026-08-14 --- .../DefaultConfirmResidencyComponent.kt | 4 +- .../model/ConfirmResidencyModel.kt | 2 +- .../ConfirmResidencyBottomSheetConfig.kt | 2 +- .../entity/CountriesListItemUM.kt | 19 -- .../selectcountry/entity/CountryItemState.kt | 26 +-- .../selectcountry/entity/CountryListUM.kt | 34 ++-- .../entity/CountryListUMController.kt | 48 ++--- .../UpdateCountryItemsLoadingTransformer.kt | 14 ++ .../UpdateCountryItemsTransformer.kt | 40 ++-- .../model/MockedCountriesData.kt | 34 ---- .../model/OnrampSelectCountryModel.kt | 47 ++++- .../selectcountry/ui/CountryListItem.kt | 136 ------------- .../ui/SelectCountryBottomSheet.kt | 180 ++++++++++++++++-- .../entity/CurrencyListController.kt | 31 ++- .../UpdateCurrencyItemsLoadingTransformer.kt | 16 ++ .../model/OnrampSelectCurrencyModel.kt | 26 ++- .../ui/SelectCurrencyBottomSheet.kt | 32 +++- 17 files changed, 367 insertions(+), 324 deletions(-) rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{selectcountry => }/entity/ConfirmResidencyBottomSheetConfig.kt (78%) delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountriesListItemUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsLoadingTransformer.kt delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsLoadingTransformer.kt diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt index 5d6b10d508..fa72ff4f67 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt @@ -15,10 +15,10 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.features.onramp.confirmresidency.model.ConfirmResidencyModel -import com.tangem.features.onramp.selectcountry.SelectCountryComponent -import com.tangem.features.onramp.selectcountry.entity.ConfirmResidencyBottomSheetConfig import com.tangem.features.onramp.confirmresidency.ui.ConfirmResidencyBottomSheet import com.tangem.features.onramp.confirmresidency.ui.ConfirmResidencyBottomSheetContent +import com.tangem.features.onramp.entity.ConfirmResidencyBottomSheetConfig +import com.tangem.features.onramp.selectcountry.SelectCountryComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt index bedc4c6094..44a4678bbe 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt @@ -9,7 +9,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.selectcountry.entity.ConfirmResidencyBottomSheetConfig +import com.tangem.features.onramp.entity.ConfirmResidencyBottomSheetConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/ConfirmResidencyBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyBottomSheetConfig.kt similarity index 78% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/ConfirmResidencyBottomSheetConfig.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyBottomSheetConfig.kt index be03ad39a9..cccafaaa8d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/ConfirmResidencyBottomSheetConfig.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyBottomSheetConfig.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.selectcountry.entity +package com.tangem.features.onramp.entity import kotlinx.serialization.Serializable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountriesListItemUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountriesListItemUM.kt deleted file mode 100644 index 04b6f8bdee..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountriesListItemUM.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.features.onramp.selectcountry.entity - -import androidx.compose.runtime.Immutable -import com.tangem.core.ui.components.fields.entity.SearchBarUM - -@Immutable -internal sealed interface CountriesListItemUM { - /** Unique ID */ - val id: Any - - data class SearchBar( - override val id: Any = "search_bar", - val searchBarUM: SearchBarUM, - ) : CountriesListItemUM - - data class Country(val state: CountryItemState) : CountriesListItemUM { - override val id = state.id - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryItemState.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryItemState.kt index 049c7dc55d..033e746ae1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryItemState.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryItemState.kt @@ -9,17 +9,19 @@ internal sealed interface CountryItemState { data class Loading(override val id: String) : CountryItemState - data class Unavailable( - override val id: String, - val flagUrl: String, - val countryName: String, - ) : CountryItemState + sealed interface WithContent : CountryItemState { + data class Unavailable( + override val id: String, + val flagUrl: String, + val countryName: String, + ) : WithContent - data class Content( - override val id: String, - val flagUrl: String, - val countryName: String, - val isSelected: Boolean, - val onClick: () -> Unit, - ) : CountryItemState + data class Content( + override val id: String, + val flagUrl: String, + val countryName: String, + val isSelected: Boolean, + val onClick: () -> Unit, + ) : WithContent + } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt index 6006c973c8..652407fced 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUM.kt @@ -1,23 +1,31 @@ package com.tangem.features.onramp.selectcountry.entity +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.fields.entity.SearchBarUM import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -internal data class CountryListUM(val items: ImmutableList) { +@Immutable +internal sealed interface CountryListUM { - /** Get search bar if it exists */ - fun getSearchBar(): CountriesListItemUM.SearchBar? { - return items.firstOrNull() as? CountriesListItemUM.SearchBar - } + val searchBarUM: SearchBarUM - /** Get tokens */ - fun getCountries(): ImmutableList { - if (getSearchBar() == null) return items + data class Loading( + override val searchBarUM: SearchBarUM, + val items: ImmutableList, + ) : CountryListUM - return if (items.size > 1) { - items.subList(fromIndex = 1, toIndex = items.size) - } else { - persistentListOf() + data class Error(override val searchBarUM: SearchBarUM, val onRetry: () -> Unit) : CountryListUM + + data class Content( + override val searchBarUM: SearchBarUM, + val items: ImmutableList, + ) : CountryListUM + + fun copySealed(searchBarUM: SearchBarUM = this.searchBarUM): CountryListUM { + return when (this) { + is Loading -> this.copy(searchBarUM = searchBarUM) + is Error -> this.copy(searchBarUM = searchBarUM) + is Content -> this.copy(searchBarUM = searchBarUM) } } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt index 88015101bb..94114f2c2a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt @@ -1,55 +1,31 @@ package com.tangem.features.onramp.selectcountry.entity -import com.tangem.features.onramp.selectcountry.entity.transformer.UpdateCountryItemsTransformer +import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.features.onramp.utils.SearchBarUMTransformer -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList +import com.tangem.utils.transformer.Transformer +import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -import javax.inject.Inject -private const val LOADING_ITEMS_COUNT = 5 - -internal class CountryListUMController @Inject constructor() { +internal class CountryListUMController( + searchBarUM: SearchBarUM, + loadingItems: ImmutableList, +) { val state: StateFlow get() = _state.asStateFlow() - private val _state: MutableStateFlow = MutableStateFlow( - value = CountryListUM( - items = getLoadingItems().map(CountriesListItemUM::Country).toImmutableList(), - ), - ) + private val _state: MutableStateFlow = + MutableStateFlow(value = CountryListUM.Loading(searchBarUM = searchBarUM, items = loadingItems)) - fun update(transformer: UpdateCountryItemsTransformer) { + fun update(transformer: Transformer) { _state.update(transformer::transform) } fun update(transformer: SearchBarUMTransformer) { _state.update { prevState -> - val searchBarItem = prevState.getSearchBar() - - if (searchBarItem != null) { - val updatedSearchBar = - searchBarItem.copy(searchBarUM = transformer.transform(searchBarItem.searchBarUM)) - - prevState.copy( - items = persistentListOf( - updatedSearchBar, - *prevState.getCountries().toTypedArray(), - ), - ) - } else { - prevState - } + val searchBarUM = transformer.transform(prevState.searchBarUM) + prevState.copySealed(searchBarUM) } } - - /** Get search bar if it exists */ - fun getSearchBar(): CountriesListItemUM.SearchBar? { - return _state.value.getSearchBar() - } - - private fun getLoadingItems(): List = - MutableList(LOADING_ITEMS_COUNT) { CountryItemState.Loading("Loading #$it") } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsLoadingTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsLoadingTransformer.kt new file mode 100644 index 0000000000..56f0b5f5cb --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsLoadingTransformer.kt @@ -0,0 +1,14 @@ +package com.tangem.features.onramp.selectcountry.entity.transformer + +import com.tangem.features.onramp.selectcountry.entity.CountryItemState +import com.tangem.features.onramp.selectcountry.entity.CountryListUM +import com.tangem.utils.transformer.Transformer +import kotlinx.collections.immutable.ImmutableList + +internal class UpdateCountryItemsLoadingTransformer( + private val loadingItems: ImmutableList, +) : Transformer { + override fun transform(prevState: CountryListUM): CountryListUM { + return CountryListUM.Loading(searchBarUM = prevState.searchBarUM, items = loadingItems) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt index 5489a9910c..0005ddd810 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt @@ -1,11 +1,7 @@ package com.tangem.features.onramp.selectcountry.entity.transformer import arrow.core.Either -import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.onramp.model.OnrampCountry -import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.selectcountry.entity.CountriesListItemUM import com.tangem.features.onramp.selectcountry.entity.CountryItemState import com.tangem.features.onramp.selectcountry.entity.CountryListUM import com.tangem.utils.transformer.Transformer @@ -15,26 +11,27 @@ internal class UpdateCountryItemsTransformer( private val maybeCountries: Either>, private val defaultCountry: OnrampCountry?, private val query: String, - private val onQueryChange: (String) -> Unit, - private val onActiveChange: (Boolean) -> Unit, + private val onRetry: () -> Unit, private val onCountryClick: (OnrampCountry) -> Unit, ) : Transformer { override fun transform(prevState: CountryListUM): CountryListUM { - val searchBarItem = prevState.getSearchBar() ?: createSearchBarItem() return maybeCountries.fold( - ifLeft = { prevState }, // TODO: [REDACTED_JIRA] + ifLeft = { CountryListUM.Error(searchBarUM = prevState.searchBarUM, onRetry = onRetry) }, ifRight = { countries -> val countriesListItems = countries.filterByQuery().toUiModels() - prevState.copy(items = (listOf(searchBarItem) + countriesListItems).toImmutableList()) + CountryListUM.Content( + searchBarUM = prevState.searchBarUM, + items = countriesListItems.toImmutableList(), + ) }, ) } - private fun List.toUiModels(): List { + private fun List.toUiModels(): List { return map { country -> - val countryItemState = if (country.onrampAvailable) { - CountryItemState.Content( + if (country.onrampAvailable) { + CountryItemState.WithContent.Content( id = country.id, flagUrl = country.image, countryName = country.name, @@ -42,9 +39,12 @@ internal class UpdateCountryItemsTransformer( onClick = { onCountryClick(country) }, ) } else { - CountryItemState.Unavailable(id = country.id, flagUrl = country.image, countryName = country.name) + CountryItemState.WithContent.Unavailable( + id = country.id, + flagUrl = country.image, + countryName = country.name, + ) } - CountriesListItemUM.Country(countryItemState) } } @@ -54,16 +54,4 @@ internal class UpdateCountryItemsTransformer( country.name.lowercase().contains(query.lowercase()) } } - - private fun createSearchBarItem(): CountriesListItemUM.SearchBar { - return CountriesListItemUM.SearchBar( - searchBarUM = SearchBarUM( - placeholderText = resourceReference(id = R.string.common_search), - query = "", - onQueryChange = onQueryChange, - isActive = false, - onActiveChange = onActiveChange, - ), - ) - } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt deleted file mode 100644 index 59c6d5d7c0..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/MockedCountriesData.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.features.onramp.selectcountry.model - -import com.tangem.features.onramp.selectcountry.entity.CountryItemState -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow - -// TODO: Temporarily. Will be deleted after implement domain -@Suppress("MagicNumber") -internal object MockedCountriesData { - - fun getCountryItems(): Flow> = flow { - delay(3_000) // simulate network call - emit(getMockedCountries()) - } - - private fun getMockedCountries(): List = MutableList(50) { index -> - if (index % 5 == 0) { - CountryItemState.Unavailable( - id = "Country #$index", - flagUrl = "", - countryName = "Country $index", - ) - } else { - CountryItemState.Content( - id = "Country #$index", - flagUrl = "", - countryName = "Country $index", - onClick = {}, - isSelected = index == 7, - ) - } - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt index 8c4915a779..6dbb2cf7d5 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt @@ -3,6 +3,7 @@ package com.tangem.features.onramp.selectcountry.model import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.onramp.GetOnrampCountriesUseCase import com.tangem.domain.onramp.GetOnrampCountryUseCase @@ -10,31 +11,39 @@ import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selectcountry.SelectCountryComponent +import com.tangem.features.onramp.selectcountry.entity.CountryItemState import com.tangem.features.onramp.selectcountry.entity.CountryListUM import com.tangem.features.onramp.selectcountry.entity.CountryListUMController +import com.tangem.features.onramp.selectcountry.entity.transformer.UpdateCountryItemsLoadingTransformer import com.tangem.features.onramp.selectcountry.entity.transformer.UpdateCountryItemsTransformer import com.tangem.features.onramp.utils.SearchManager import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject @ComponentScoped -@Suppress("LongParameterList") internal class OnrampSelectCountryModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val searchManager: SearchManager, - private val countryListUMController: CountryListUMController, private val getOnrampCountriesUseCase: GetOnrampCountriesUseCase, private val saveDefaultCountryUseCase: OnrampSaveDefaultCountryUseCase, private val getOnrampCountryUseCase: GetOnrampCountryUseCase, paramsContainer: ParamsContainer, ) : Model() { - val state: StateFlow = countryListUMController.state + val state: StateFlow get() = countryListUMController.state private val params: SelectCountryComponent.Params = paramsContainer.require() + private val countryListUMController = CountryListUMController( + searchBarUM = createSearchBarUM(), + loadingItems = loadingItems, + ) + private val refreshTrigger = MutableSharedFlow() init { modelScope.launch { subscribeOnUpdateState() } @@ -44,9 +53,10 @@ internal class OnrampSelectCountryModel @Inject constructor( params.onDismiss() } + @OptIn(ExperimentalCoroutinesApi::class) private suspend fun subscribeOnUpdateState() { combine( - flow = flowOf(getOnrampCountriesUseCase.invoke()), + flow = refreshTrigger.onStart { emit(Unit) }.flatMapLatest { flowOf(getOnrampCountriesUseCase.invoke()) }, flow2 = getOnrampCountryUseCase.invoke(), flow3 = searchManager.query, ) { maybeCountries, maybeCountry, query -> @@ -54,8 +64,7 @@ internal class OnrampSelectCountryModel @Inject constructor( maybeCountries = maybeCountries, defaultCountry = maybeCountry.getOrNull(), query = query, - onQueryChange = ::onSearchQueryChange, - onActiveChange = ::onSearchBarActiveChange, + onRetry = ::onRetry, onCountryClick = ::saveCountry, ) } @@ -70,9 +79,14 @@ internal class OnrampSelectCountryModel @Inject constructor( } } + private fun onRetry() { + modelScope.launch { refreshTrigger.emit(Unit) } + countryListUMController.update(UpdateCountryItemsLoadingTransformer(loadingItems)) + } + private fun onSearchQueryChange(newQuery: String) { - val searchBar = countryListUMController.getSearchBar() - if (searchBar?.searchBarUM?.query == newQuery) return + val searchBarUM = countryListUMController.state.value.searchBarUM + if (searchBarUM.query == newQuery) return modelScope.launch { countryListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery)) @@ -89,4 +103,21 @@ internal class OnrampSelectCountryModel @Inject constructor( ), ) } + + private fun createSearchBarUM(): SearchBarUM { + return SearchBarUM( + placeholderText = resourceReference(R.string.onramp_country_search), + query = "", + onQueryChange = ::onSearchQueryChange, + isActive = false, + onActiveChange = ::onSearchBarActiveChange, + ) + } + + private companion object { + private const val LOADING_ITEMS_COUNT = 5 + val loadingItems: ImmutableList = MutableList(LOADING_ITEMS_COUNT) { + CountryItemState.Loading("Loading #$it") + }.toImmutableList() + } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt deleted file mode 100644 index 7bc00833a0..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/CountryListItem.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.tangem.features.onramp.selectcountry.ui - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -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.composed -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextOverflow -import coil.compose.AsyncImage -import com.tangem.core.ui.components.CircleShimmer -import com.tangem.core.ui.components.RectangleShimmer -import com.tangem.core.ui.components.fields.SearchBar -import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.selectcountry.entity.CountriesListItemUM -import com.tangem.features.onramp.selectcountry.entity.CountryItemState - -@Composable -internal fun CountryListItem(state: CountriesListItemUM, modifier: Modifier = Modifier) { - when (state) { - is CountriesListItemUM.Country -> CountryItem( - state = state.state, - modifier = modifier - .countryClickable(state.state) - .padding(all = TangemTheme.dimens.spacing16), - ) - is CountriesListItemUM.SearchBar -> SearchBar( - state = state.searchBarUM, - modifier = modifier.padding(all = TangemTheme.dimens.spacing16), - ) - } -} - -@Composable -private fun CountryItem(state: CountryItemState, modifier: Modifier = Modifier) { - when (state) { - is CountryItemState.Content -> ContentCountryItem(state = state, modifier = modifier) - is CountryItemState.Loading -> LoadingCountryItem(modifier) - is CountryItemState.Unavailable -> UnavailableCountryItem(state = state, modifier = modifier) - } -} - -@Composable -private fun ContentCountryItem(state: CountryItemState.Content, modifier: Modifier = Modifier) { - Row( - modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12), - verticalAlignment = Alignment.CenterVertically, - ) { - AsyncImage( - modifier = Modifier.size(TangemTheme.dimens.size36), - model = state.flagUrl, - contentDescription = null, - ) - Text( - text = state.countryName, - modifier = Modifier.weight(1F), - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.primary1, - ) - if (state.isSelected) { - Icon( - painter = painterResource(id = R.drawable.ic_check_24), - contentDescription = null, - modifier = Modifier.size(TangemTheme.dimens.size24), - tint = TangemTheme.colors.icon.accent, - ) - } - } -} - -@Composable -private fun LoadingCountryItem(modifier: Modifier = Modifier) { - Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12)) { - CircleShimmer(modifier = Modifier.size(TangemTheme.dimens.size36)) - RectangleShimmer( - modifier = Modifier - .align(Alignment.CenterVertically) - .size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12), - radius = TangemTheme.dimens.radius4, - ) - } -} - -@Composable -@Suppress("MagicNumber") -private fun UnavailableCountryItem(state: CountryItemState.Unavailable, modifier: Modifier = Modifier) { - Row( - modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12), - verticalAlignment = Alignment.CenterVertically, - ) { - AsyncImage( - modifier = Modifier - .size(TangemTheme.dimens.size36) - .alpha(0.4F), - model = state.flagUrl, - contentDescription = null, - ) - Text( - modifier = Modifier.weight(1F), - text = state.countryName, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.disabled, - ) - Text( - text = "Unavailable", - maxLines = 1, - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.tertiary, - ) - } -} - -@OptIn(ExperimentalFoundationApi::class) -private fun Modifier.countryClickable(state: CountryItemState): Modifier = composed { - when (state) { - is CountryItemState.Content -> combinedClickable(onClick = state.onClick) - is CountryItemState.Loading, - is CountryItemState.Unavailable, - -> this - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt index 89c664ce1e..6fbf4c8d16 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt @@ -1,13 +1,32 @@ package com.tangem.features.onramp.selectcountry.ui -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import coil.compose.AsyncImage +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.buttons.SecondarySmallButton +import com.tangem.core.ui.components.buttons.SmallButtonConfig +import com.tangem.core.ui.components.fields.SearchBar +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.selectcountry.entity.CountryItemState import com.tangem.features.onramp.selectcountry.entity.CountryListUM @Composable @@ -20,19 +39,156 @@ internal fun SelectCountryBottomSheet(config: TangemBottomSheetConfig, content: @Composable internal fun OnrampCountryList(state: CountryListUM, modifier: Modifier = Modifier) { - LazyColumn(modifier = modifier) { - items( - items = state.items, - key = { item -> item.id }, - contentType = { item -> item::class.java }, - itemContent = { item -> - CountryListItem( - state = item, + LazyColumn( + modifier = modifier, + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), + ) { + item(key = "search_bar") { + SearchBar( + state = state.searchBarUM, + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing20), + ) + } + when (state) { + is CountryListUM.Content -> countryListWithContent(state = state) + is CountryListUM.Error -> countryListError(state = state) + is CountryListUM.Loading -> countryListLoading(state = state) + } + } +} + +private fun LazyListScope.countryListError(state: CountryListUM.Error) { + item(key = "error_content") { + Column( + modifier = Modifier + .fillParentMaxHeight() + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text( + text = stringResource(id = R.string.markets_loading_error_title), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.tertiary, + ) + SecondarySmallButton( + modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), + config = SmallButtonConfig( + text = resourceReference(id = R.string.try_to_load_data_again_button_title), + onClick = state.onRetry, + ), + ) + } + } +} + +private fun LazyListScope.countryListLoading(state: CountryListUM.Loading) { + items( + items = state.items, + key = { item -> item.id }, + contentType = { item -> item::class.java }, + itemContent = { _ -> LoadingCountryItem(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing16)) }, + ) +} + +private fun LazyListScope.countryListWithContent(state: CountryListUM.Content) { + items( + items = state.items, + key = { item -> item.id }, + contentType = { item -> item::class.java }, + itemContent = { item -> + when (item) { + is CountryItemState.WithContent.Content -> ContentCountryItem( modifier = Modifier - .animateItem() - .fillMaxWidth(), + .fillMaxWidth() + .clickable(onClick = item.onClick) + .padding(vertical = TangemTheme.dimens.spacing16), + state = item, ) - }, + is CountryItemState.WithContent.Unavailable -> UnavailableCountryItem( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = TangemTheme.dimens.spacing16), + state = item, + ) + } + }, + ) +} + +@Composable +private fun LoadingCountryItem(modifier: Modifier = Modifier) { + Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12)) { + CircleShimmer(modifier = Modifier.size(TangemTheme.dimens.size36)) + TextShimmer( + modifier = Modifier + .align(Alignment.CenterVertically) + .width(width = TangemTheme.dimens.size70), + style = TangemTheme.typography.subtitle2, + radius = TangemTheme.dimens.radius4, + ) + } +} + +@Composable +private fun ContentCountryItem(state: CountryItemState.WithContent.Content, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + ) { + AsyncImage( + modifier = Modifier.size(TangemTheme.dimens.size36), + model = state.flagUrl, + contentDescription = null, + ) + Text( + text = state.countryName, + modifier = Modifier.weight(1F), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + ) + if (state.isSelected) { + Icon( + painter = painterResource(id = R.drawable.ic_check_24), + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size24), + tint = TangemTheme.colors.icon.accent, + ) + } + } +} + +@Composable +@Suppress("MagicNumber") +private fun UnavailableCountryItem(state: CountryItemState.WithContent.Unavailable, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + ) { + AsyncImage( + modifier = Modifier + .size(TangemTheme.dimens.size36) + .alpha(0.4F), + model = state.flagUrl, + contentDescription = null, + ) + Text( + modifier = Modifier.weight(1F), + text = state.countryName, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.disabled, + ) + Text( + text = "Unavailable", + maxLines = 1, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, ) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt index 157e5a9133..eaad4b3365 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt @@ -1,21 +1,27 @@ package com.tangem.features.onramp.selectcurrency.entity import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.core.ui.extensions.stringReference -import com.tangem.features.onramp.selectcurrency.entity.transformer.UpdateCurrencyItemsTransformer import com.tangem.features.onramp.utils.SearchBarUMTransformer +import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -internal class CurrencyListController(private val currencySearchBarUM: SearchBarUM) { +internal class CurrencyListController( + currencySearchBarUM: SearchBarUM, + loadingSections: ImmutableList>, +) { val state: StateFlow get() = _state.asStateFlow() - private val _state: MutableStateFlow = MutableStateFlow(value = getInitialState()) + private val _state: MutableStateFlow = MutableStateFlow( + value = CurrenciesListUM.Loading( + searchBarUM = currencySearchBarUM, + sections = loadingSections, + ), + ) - fun update(transformer: UpdateCurrencyItemsTransformer) { + fun update(transformer: Transformer) { _state.update(transformer::transform) } @@ -25,17 +31,4 @@ internal class CurrencyListController(private val currencySearchBarUM: SearchBar prevState.copySealed(searchBarUM) } } - - private fun getInitialState(): CurrenciesListUM { - return CurrenciesListUM.Loading( - searchBarUM = currencySearchBarUM, - sections = listOf( - CurrenciesSection(stringReference("Popular fiats"), items = createLoadingItems("popular")), - CurrenciesSection(stringReference("Other currencies"), items = createLoadingItems("other")), - ).toImmutableList(), - ) - } - - private fun createLoadingItems(prefix: String, size: Int = 5): ImmutableList = - MutableList(size) { index -> CurrencyItemState.Loading("$prefix#$index") }.toImmutableList() } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsLoadingTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsLoadingTransformer.kt new file mode 100644 index 0000000000..50eddcba44 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsLoadingTransformer.kt @@ -0,0 +1,16 @@ +package com.tangem.features.onramp.selectcurrency.entity.transformer + +import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM +import com.tangem.features.onramp.selectcurrency.entity.CurrenciesSection +import com.tangem.features.onramp.selectcurrency.entity.CurrencyItemState +import com.tangem.utils.transformer.Transformer +import kotlinx.collections.immutable.ImmutableList + +internal class UpdateCurrencyItemsLoadingTransformer( + private val loadingSections: ImmutableList>, +) : Transformer { + + override fun transform(prevState: CurrenciesListUM): CurrenciesListUM { + return CurrenciesListUM.Loading(searchBarUM = prevState.searchBarUM, sections = loadingSections) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt index b6668e3f40..35b364808d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt @@ -5,18 +5,24 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.onramp.GetOnrampCurrenciesUseCase import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase import com.tangem.domain.onramp.model.OnrampCurrency import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selectcurrency.SelectCurrencyComponent import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM +import com.tangem.features.onramp.selectcurrency.entity.CurrenciesSection +import com.tangem.features.onramp.selectcurrency.entity.CurrencyItemState import com.tangem.features.onramp.selectcurrency.entity.CurrencyListController +import com.tangem.features.onramp.selectcurrency.entity.transformer.UpdateCurrencyItemsLoadingTransformer import com.tangem.features.onramp.selectcurrency.entity.transformer.UpdateCurrencyItemsTransformer import com.tangem.features.onramp.utils.SearchManager import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -34,7 +40,10 @@ internal class OnrampSelectCurrencyModel @Inject constructor( val state: StateFlow get() = controller.state private val params: SelectCurrencyComponent.Params = paramsContainer.require() - private val controller = CurrencyListController(currencySearchBarUM = createSearchBarUM()) + private val controller = CurrencyListController( + currencySearchBarUM = createSearchBarUM(), + loadingSections = loadingSections, + ) private val refreshTrigger = MutableSharedFlow() init { @@ -70,9 +79,8 @@ internal class OnrampSelectCurrencyModel @Inject constructor( } private fun onRetry() { - modelScope.launch { - refreshTrigger.emit(Unit) - } + modelScope.launch { refreshTrigger.emit(Unit) } + controller.update(UpdateCurrencyItemsLoadingTransformer(loadingSections)) } private fun onSearchQueryChange(newQuery: String) { @@ -104,4 +112,14 @@ internal class OnrampSelectCurrencyModel @Inject constructor( onActiveChange = ::onSearchBarActiveChange, ) } + + private companion object { + val loadingSections = listOf( + CurrenciesSection(stringReference("Popular fiats"), items = createLoadingItems("popular")), + CurrenciesSection(stringReference("Other currencies"), items = createLoadingItems("other")), + ).toImmutableList() + + private fun createLoadingItems(prefix: String, size: Int = 5): ImmutableList = + MutableList(size) { index -> CurrencyItemState.Loading("$prefix#$index") }.toImmutableList() + } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt index 88dfc1d6d9..8177c32631 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource import androidx.compose.ui.util.fastForEach import coil.compose.AsyncImage import com.tangem.core.ui.components.CircleShimmer @@ -18,9 +19,13 @@ import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.buttons.SecondarySmallButton +import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM import com.tangem.features.onramp.selectcurrency.entity.CurrencyItemState @@ -46,12 +51,37 @@ internal fun OnrampCurrencyList(state: CurrenciesListUM, modifier: Modifier = Mo } when (state) { is CurrenciesListUM.Content -> currencyListContent(state = state) - is CurrenciesListUM.Error -> TODO() + is CurrenciesListUM.Error -> currencyListError(state = state) is CurrenciesListUM.Loading -> currencyListLoading(state = state) } } } +private fun LazyListScope.currencyListError(state: CurrenciesListUM.Error) { + item(key = "error_content") { + Column( + modifier = Modifier + .fillParentMaxHeight() + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text( + text = stringResource(id = R.string.markets_loading_error_title), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.tertiary, + ) + SecondarySmallButton( + modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), + config = SmallButtonConfig( + text = resourceReference(id = R.string.try_to_load_data_again_button_title), + onClick = state.onRetry, + ), + ) + } + } +} + private fun LazyListScope.currencyListLoading(state: CurrenciesListUM.Loading) { state.sections.fastForEach { section -> item(key = section.title.toString()) { GroupTitle(title = section.title.resolveReference()) } From 145266c5821163a9674281365afd11c0d1d45815 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 14:13:07 +0500 Subject: [PATCH 26/33] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ecf6a68855..5988269248 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -90,7 +90,7 @@ markdownComposeView = "0.5.4" # region Tangem tangemBlockchainSdk = "develop-848" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-395" +tangemCardSdk = "develop-397" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem17" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From 9489035904e7c164248ce8d59171490f32adceec Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 15:00:16 +0400 Subject: [PATCH 27/33] Updated on 2026-08-14 --- .../com/tangem/common/routing/AppRoute.kt | 14 ++- .../impl/model/TokenActionsHandler.kt | 2 +- .../swap/model/SwapSelectTokensModel.kt | 16 +++- features/swap/presentation/build.gradle.kts | 1 + .../tangem/feature/swap/ui/StateBuilder.kt | 28 +++--- .../feature/swap/viewmodels/SwapViewModel.kt | 92 ++++++++++++------- .../viewmodels/TokenDetailsViewModel.kt | 2 +- .../WalletCurrencyActionsClickIntents.kt | 2 +- 8 files changed, 104 insertions(+), 53 deletions(-) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index d69c5ed969..f2903ad378 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -219,16 +219,24 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class Swap( - val currency: CryptoCurrency, + val currencyFrom: CryptoCurrency, + val currencyTo: CryptoCurrency? = null, val userWalletId: UserWalletId, val isInitialReverseOrder: Boolean = false, - ) : AppRoute(path = "/swap/${currency.id.value}/${userWalletId.stringValue}/$isInitialReverseOrder"), + ) : AppRoute( + path = "/swap" + + "/${currencyFrom.id.value}" + + "/${currencyTo?.id?.value}" + + "/${userWalletId.stringValue}" + + "/$isInitialReverseOrder", + ), RouteBundleParams { override fun getBundle(): Bundle = bundle(serializer()) companion object { - const val CURRENCY_BUNDLE_KEY = "currency" + const val CURRENCY_FROM_KEY = "currencyFrom" + const val CURRENCY_TO_KEY = "currencyTo" const val USER_WALLET_ID_KEY = "userWalletId" const val IS_INITIAL_REVERSE_ORDER = "isInitialReverseOrder" } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt index 8d7bc12e6c..2333f6b48f 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt @@ -149,7 +149,7 @@ internal class TokenActionsHandler @AssistedInject constructor( private fun onExchangeClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) { router.push( AppRoute.Swap( - currency = cryptoCurrencyData.status.currency, + currencyFrom = cryptoCurrencyData.status.currency, userWalletId = cryptoCurrencyData.userWallet.walletId, isInitialReverseOrder = true, ), diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt index a5208ac04a..40a3b14a91 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt @@ -1,8 +1,12 @@ package com.tangem.features.onramp.swap.model +import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.onramp.component.SwapSelectTokensComponent import com.tangem.features.onramp.swap.entity.SwapSelectTokensController import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM import com.tangem.features.onramp.swap.entity.transformer.RemoveSelectedFromTokenTransformer @@ -15,8 +19,10 @@ import javax.inject.Inject @Suppress("LongParameterList") internal class SwapSelectTokensModel @Inject constructor( + paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, private val controller: SwapSelectTokensController, + private val router: Router, ) : Model() { val state: StateFlow = controller.state @@ -26,6 +32,8 @@ internal class SwapSelectTokensModel @Inject constructor( private val _fromCurrencyStatus = MutableStateFlow(value = null) private val _toCurrencyStatus = MutableStateFlow(value = null) + private val params = paramsContainer.require() + /** * Select "from" token * @@ -54,7 +62,13 @@ internal class SwapSelectTokensModel @Inject constructor( controller.update(transformer = SelectToTokenTransformer(selectedTokenItemState)) - // TODO: [REDACTED_JIRA] + router.push( + route = AppRoute.Swap( + currencyFrom = requireNotNull(fromCurrencyStatus.value).currency, + currencyTo = status.currency, + userWalletId = params.userWalletId, + ), + ) } private fun onRemoveClick() { diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index 3b0dbe2d6d..84cbe75297 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(projects.features.swap.domain) implementation(projects.features.swap.domain.api) implementation(projects.features.swap.domain.models) + implementation(projects.features.wallet.api) /** AndroidX */ implementation(deps.androidx.activity.compose) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index c270a8d837..6f3ae57e47 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -63,9 +63,13 @@ internal class StateBuilder( appCurrencyProvider = appCurrencyProvider, ) - fun createInitialLoadingState(initialCurrency: CryptoCurrency, networkInfo: NetworkInfo): SwapStateHolder { + fun createInitialLoadingState( + initialCurrencyFrom: CryptoCurrency, + initialCurrencyTo: CryptoCurrency?, + fromNetworkInfo: NetworkInfo, + ): SwapStateHolder { return SwapStateHolder( - blockchainId = networkInfo.blockchainId, + blockchainId = fromNetworkInfo.blockchainId, sendCardData = SwapCardState.SwapCardData( type = TransactionCardType.Inputtable( onAmountChanged = actions.onAmountChanged, @@ -75,27 +79,27 @@ internal class StateBuilder( amountEquivalent = null, amountTextFieldValue = null, token = null, - tokenIconUrl = initialCurrency.iconUrl, - tokenCurrency = initialCurrency.symbol, - coinId = initialCurrency.network.backendId, + tokenIconUrl = initialCurrencyFrom.iconUrl, + tokenCurrency = initialCurrencyFrom.symbol, + coinId = initialCurrencyFrom.network.backendId, canSelectAnotherToken = false, - isNotNativeToken = initialCurrency is CryptoCurrency.Token, + isNotNativeToken = initialCurrencyFrom is CryptoCurrency.Token, balance = "", - networkIconRes = getActiveIconRes(initialCurrency.network.id.value), + networkIconRes = getActiveIconRes(initialCurrencyFrom.network.id.value), isBalanceHidden = true, ), receiveCardData = SwapCardState.SwapCardData( type = TransactionCardType.ReadOnly(), amountEquivalent = null, - tokenIconUrl = "", - tokenCurrency = "", + tokenIconUrl = initialCurrencyTo?.iconUrl, + tokenCurrency = initialCurrencyTo?.symbol ?: "", token = null, amountTextFieldValue = null, canSelectAnotherToken = false, balance = "", - isNotNativeToken = false, - networkIconRes = null, - coinId = null, + isNotNativeToken = initialCurrencyTo is CryptoCurrency.Token, + networkIconRes = initialCurrencyTo?.let { getActiveIconRes(it.network.id.value) }, + coinId = initialCurrencyTo?.network?.backendId, isBalanceHidden = true, ), fee = FeeItemState.Empty, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index c4854ddb29..ed003efb7c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -49,6 +49,7 @@ import com.tangem.feature.swap.router.SwapNavScreen import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.ui.StateBuilder import com.tangem.feature.swap.utils.formatToUIRepresentation +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.utils.Provider import com.tangem.utils.coroutines.* import com.tangem.utils.isNullOrZero @@ -83,13 +84,26 @@ internal class SwapViewModel @Inject constructor( private val getCardInfoUseCase: GetCardInfoUseCase, private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, + private val walletFeatureToggles: WalletFeatureToggles, swapInteractorFactory: SwapInteractor.Factory, - savedStateHandle: SavedStateHandle, + private val savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver { - private val initialCryptoCurrency: CryptoCurrency = savedStateHandle.get(AppRoute.Swap.CURRENCY_BUNDLE_KEY) - ?.unbundle(CryptoCurrency.serializer()) - ?: error("no expected parameter CryptoCurrency found") + private val initialCurrencyFrom: CryptoCurrency + get() { + return savedStateHandle.get(AppRoute.Swap.CURRENCY_FROM_KEY) + ?.unbundle(CryptoCurrency.serializer()) + ?: error("no expected parameter CryptoCurrency (from) found") + } + + private val initialCurrencyTo: CryptoCurrency? + get() = if (walletFeatureToggles.isMainActionButtonsEnabled) { + savedStateHandle.get(AppRoute.Swap.CURRENCY_TO_KEY) + ?.unbundle(CryptoCurrency.serializer()) + ?: error("no expected parameter CryptoCurrency (to) found") + } else { + null + } private val userWalletId: UserWalletId = savedStateHandle.get(AppRoute.Swap.USER_WALLET_ID_KEY) ?.unbundle(UserWalletId.serializer()) @@ -100,7 +114,8 @@ internal class SwapViewModel @Inject constructor( private val swapInteractor = swapInteractorFactory.create(userWalletId) - private lateinit var initialCryptoCurrencyStatus: CryptoCurrencyStatus + private lateinit var initialFromStatus: CryptoCurrencyStatus + private var initialToStatus: CryptoCurrencyStatus? = null private var userWallet: UserWallet by Delegates.notNull() private var isBalanceHidden = true @@ -122,8 +137,9 @@ internal class SwapViewModel @Inject constructor( var uiState: SwapStateHolder by mutableStateOf( stateBuilder.createInitialLoadingState( - initialCurrency = initialCryptoCurrency, - networkInfo = blockchainInteractor.getBlockchainInfo(initialCryptoCurrency.network.backendId), + initialCurrencyFrom = initialCurrencyFrom, + initialCurrencyTo = initialCurrencyTo, + fromNetworkInfo = blockchainInteractor.getBlockchainInfo(initialCurrencyFrom.network.backendId), ), ) private set @@ -151,14 +167,16 @@ internal class SwapViewModel @Inject constructor( init { viewModelScope.launch(dispatchers.io) { - val cryptoCurrencyStatus = - getCryptoCurrencyStatusUseCase(userWalletId, initialCryptoCurrency.id).getOrNull() + val fromStatus = getCryptoCurrencyStatusUseCase(userWalletId, initialCurrencyFrom.id).getOrNull() + val toStatus = initialCurrencyTo?.let { getCryptoCurrencyStatusUseCase(userWalletId, it.id).getOrNull() } val wallet = getUserWalletUseCase(userWalletId).getOrNull() - if (cryptoCurrencyStatus == null || wallet == null) { + val isStatusToNull = walletFeatureToggles.isMainActionButtonsEnabled && toStatus == null + if (fromStatus == null || wallet == null || isStatusToNull) { uiState = stateBuilder.addAlert(uiState = uiState, onDismiss = swapRouter::back) } else { userWallet = wallet - initialCryptoCurrencyStatus = cryptoCurrencyStatus + initialFromStatus = fromStatus + initialToStatus = toStatus initTokens(isInitiallyReversed) } } @@ -179,7 +197,7 @@ internal class SwapViewModel @Inject constructor( } fun onScreenOpened() { - analyticsEventHandler.send(SwapEvents.SwapScreenOpened(initialCryptoCurrency.symbol)) + analyticsEventHandler.send(SwapEvents.SwapScreenOpened(initialCurrencyFrom.symbol)) } fun setRouter(router: SwapRouter) { @@ -205,14 +223,19 @@ internal class SwapViewModel @Inject constructor( private fun initTokens(isReverseFromTo: Boolean) { viewModelScope.launch(dispatchers.main) { runCatching(dispatchers.io) { - swapInteractor.getTokensDataState(initialCryptoCurrency) + swapInteractor.getTokensDataState(initialCurrencyFrom) }.onSuccess { state -> updateTokensState(state) - val selectedCurrency = swapInteractor.getInitialCurrencyToSwap( - initialCryptoCurrency = initialCryptoCurrency, - state = state, - isReverseFromTo = isReverseFromTo, - ) + val selectedCurrency = if (walletFeatureToggles.isMainActionButtonsEnabled) { + initialToStatus + } else { + swapInteractor.getInitialCurrencyToSwap( + initialCryptoCurrency = initialCurrencyFrom, + state = state, + isReverseFromTo = isReverseFromTo, + ) + } + applyInitialTokenChoice( state = state, selectedCurrency = selectedCurrency, @@ -248,9 +271,10 @@ internal class SwapViewModel @Inject constructor( (it as? ExpressException)?.expressDataError?.code ?: ExpressDataError.UnknownError.code, ) { uiState = stateBuilder.createInitialLoadingState( - initialCurrency = initialCryptoCurrency, - networkInfo = blockchainInteractor.getBlockchainInfo( - initialCryptoCurrency.network.backendId, + initialCurrencyFrom = initialCurrencyFrom, + initialCurrencyTo = initialCurrencyTo, + fromNetworkInfo = blockchainInteractor.getBlockchainInfo( + initialCurrencyFrom.network.backendId, ), ) initTokens(isReverseFromTo) @@ -269,15 +293,15 @@ internal class SwapViewModel @Inject constructor( analyticsEventHandler.send(SwapEvents.NoticeNoAvailableTokensToSwap) uiState = stateBuilder.createNoAvailableTokensToSwapState( uiStateHolder = uiState, - fromToken = initialCryptoCurrencyStatus, + fromToken = initialFromStatus, ) return } isOrderReversed = isReverseFromTo val (fromCurrencyStatus, toCurrencyStatus) = if (isOrderReversed) { - selectedCurrency to initialCryptoCurrencyStatus + selectedCurrency to initialFromStatus } else { - initialCryptoCurrencyStatus to selectedCurrency + initialFromStatus to selectedCurrency } dataState = dataState.copy( fromCryptoCurrency = fromCurrencyStatus, @@ -297,7 +321,7 @@ internal class SwapViewModel @Inject constructor( uiState = stateBuilder.addTokensToState( uiState = uiState, tokensDataState = tokensDataState, - fromToken = dataState.fromCryptoCurrency?.currency ?: initialCryptoCurrency, + fromToken = dataState.fromCryptoCurrency?.currency ?: initialCurrencyFrom, ) } @@ -314,7 +338,7 @@ internal class SwapViewModel @Inject constructor( uiState, fromToken.currency, toToken.currency, - initialCryptoCurrency.id.value, + initialCurrencyFrom.id.value, ) } singleTaskScheduler.scheduleTask( @@ -410,7 +434,7 @@ internal class SwapViewModel @Inject constructor( if (uiState.warnings.any { it is SwapWarning.UnableToCoverFeeWarning }) { analyticsEventHandler.send( SwapEvents.NoticeNotEnoughFee( - token = initialCryptoCurrency.symbol, + token = initialCurrencyFrom.symbol, blockchain = fromToken.currency.network.name, ), ) @@ -448,7 +472,7 @@ internal class SwapViewModel @Inject constructor( } analyticsEventHandler.send( SwapEvents.NoticeProviderError( - sendToken = "${initialCryptoCurrency.network.backendId}:${initialCryptoCurrency.symbol}", + sendToken = "${initialCurrencyFrom.network.backendId}:${initialCurrencyFrom.symbol}", receiveToken = receiveToken ?: "", provider = provider, errorCode = error.code, @@ -586,7 +610,7 @@ internal class SwapViewModel @Inject constructor( swapRouter.openUrl(url) } analyticsEventHandler.send( - event = SwapEvents.ButtonExplore(initialCryptoCurrency.symbol), + event = SwapEvents.ButtonExplore(initialCurrencyFrom.symbol), ) }, onStatusClick = { @@ -594,7 +618,7 @@ internal class SwapViewModel @Inject constructor( if (!txExternalUrl.isNullOrBlank()) { swapRouter.openUrl(txExternalUrl) analyticsEventHandler.send( - event = SwapEvents.ButtonStatus(initialCryptoCurrency.symbol), + event = SwapEvents.ButtonStatus(initialCurrencyFrom.symbol), ) } }, @@ -765,7 +789,7 @@ internal class SwapViewModel @Inject constructor( val toToken: CryptoCurrencyStatus if (isOrderReversed) { fromToken = foundToken.currencyStatus - toToken = initialCryptoCurrencyStatus + toToken = initialFromStatus val newToken = fromToken.currency as? CryptoCurrency.Coin if (newToken != null) { @@ -776,7 +800,7 @@ internal class SwapViewModel @Inject constructor( ) } } else { - fromToken = initialCryptoCurrencyStatus + fromToken = initialFromStatus toToken = foundToken.currencyStatus val newToken = toToken.currency as? CryptoCurrency.Coin @@ -1264,7 +1288,7 @@ internal class SwapViewModel @Inject constructor( private fun onFailedTxEmailClick(errorMessage: String) { viewModelScope.launch { - val network = initialCryptoCurrencyStatus.currency.network + val network = initialFromStatus.currency.network val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrElse { error("CardInfo must be not null") } saveBlockchainErrorUseCase( @@ -1273,7 +1297,7 @@ internal class SwapViewModel @Inject constructor( blockchainId = network.id.value, derivationPath = network.derivationPath.value, destinationAddress = dataState.swapDataModel?.transaction?.txTo.orEmpty(), - tokenSymbol = initialCryptoCurrency.symbol, + tokenSymbol = initialCurrencyFrom.symbol, amount = dataState.amount.orEmpty(), fee = dataState.selectedFee?.feeCryptoFormatted.orEmpty(), ), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index b648664a77..c07f123af4 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -621,7 +621,7 @@ internal class TokenDetailsViewModel @Inject constructor( if (handleUnavailabilityReason(unavailabilityReason)) return - appRouter.push(AppRoute.Swap(currency = cryptoCurrency, userWalletId = userWalletId)) + appRouter.push(AppRoute.Swap(currencyFrom = cryptoCurrency, userWalletId = userWalletId)) } override fun onDismissDialog() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 1a4283bbd6..23d45eab8b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -380,7 +380,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( appRouter.push( AppRoute.Swap( - currency = cryptoCurrencyStatus.currency, + currencyFrom = cryptoCurrencyStatus.currency, userWalletId = userWalletId, ), ) From 1e7c54a66268a720f1b3713ad2df0dbea2ece5ab Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Nov 2024 14:00:09 +0300 Subject: [PATCH 28/33] Updated on 2026-08-14 --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 3a969ad8fe..3e279d9f6c 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -versionName=5.18.0 \ No newline at end of file +versionName=5.19.0 \ No newline at end of file From c6cdb300e6ca26cd96828e31778949c0e01116ed Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Nov 2024 14:00:40 +0300 Subject: [PATCH 29/33] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 2dc8d50df9..f7af0b38fc 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -21,7 +21,7 @@ }, { "name": "MAIN_ACTION_BUTTONS_ENABLED", - "version": "undefined" + "version": "5.19.0" }, { "name": "ONBOARDING_CODE_REFACTORING_ENABLED", From f011cba752e5c18d204235c84b5fa9943ff1134d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Nov 2024 14:42:17 +0500 Subject: [PATCH 30/33] Updated on 2026-08-14 --- .../tap/di/domain/OnrampDomainModule.kt | 6 ++ .../middlewares/TradeCryptoMiddleware.kt | 10 +- .../tangem/tap/routing/utils/ChildFactory.kt | 2 +- .../com/tangem/common/routing/AppRoute.kt | 4 +- .../domain/onramp/model/OnrampAvailability.kt | 8 ++ .../domain/onramp/model/OnrampCountry.kt | 3 + .../domain/onramp/model/OnrampCurrency.kt | 3 + .../onramp/CheckOnrampAvailabilityUseCase.kt | 24 +++++ .../onramp/component/OnrampComponent.kt | 2 +- .../ConfirmResidencyComponent.kt | 8 +- .../DefaultConfirmResidencyComponent.kt | 2 +- .../model/ConfirmResidencyModel.kt | 33 ++++-- .../onramp/main/DefaultOnrampMainComponent.kt | 66 ++++++++++++ .../onramp/main/OnrampMainComponent.kt | 11 ++ .../main/di/OnrampMainComponentModelModule.kt | 20 ++++ .../main/di/OnrampMainComponentModule.kt | 18 ++++ .../entity/OnrampMainBottomSheetConfig.kt | 10 ++ .../main/entity/OnrampMainComponentUM.kt | 54 ++++++++++ .../onramp/main/entity/OnrampMainTopBarUM.kt | 10 ++ .../OnrampAvailabilityTransformer.kt | 53 ++++++++++ .../main/model/OnrampMainComponentModel.kt | 65 ++++++++++++ .../main/ui/OnrampMainComponentContent.kt | 100 ++++++++++++++++++ .../onramp/root/DefaultOnrampComponent.kt | 21 +++- 23 files changed, 505 insertions(+), 28 deletions(-) create mode 100644 domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampAvailability.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainTopBarUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/transformer/OnrampAvailabilityTransformer.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt index 29cd227fdd..f15fd1b0c0 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -41,4 +41,10 @@ internal object OnrampDomainModule { fun provideOnrampSaveDefaultCountryUseCase(onrampRepository: OnrampRepository): OnrampSaveDefaultCountryUseCase { return OnrampSaveDefaultCountryUseCase(onrampRepository) } + + @Provides + @Singleton + fun provideCheckOnrampAvailabilityUseCase(onrampRepository: OnrampRepository): CheckOnrampAvailabilityUseCase { + return CheckOnrampAvailabilityUseCase(onrampRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index a21351a6b9..b68935424e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -55,11 +55,15 @@ object TradeCryptoMiddleware { private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) { val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled - if (isOnrampEnabled) proceedWithOnramp() else proceedWithLegacyBuyAction(state, action) + if (isOnrampEnabled) { + proceedWithOnramp(action.cryptoCurrencyStatus.currency) + } else { + proceedWithLegacyBuyAction(state, action) + } } - private fun proceedWithOnramp() { - store.dispatchNavigationAction { push(AppRoute.Onramp) } + private fun proceedWithOnramp(cryptoCurrency: CryptoCurrency) { + store.dispatchNavigationAction { push(AppRoute.Onramp(cryptoCurrency)) } } private fun proceedWithLegacyBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) { diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index e53226e658..8678616017 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -199,7 +199,7 @@ internal class ChildFactory @Inject constructor( is AppRoute.Onramp -> { route.asComponentChild( contextProvider = contextProvider(route, contextFactory), - params = OnrampComponent.Params(), + params = OnrampComponent.Params(route.currency.name), componentFactory = onrampComponentFactory, ) } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index f2903ad378..66993b210a 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -295,7 +295,9 @@ sealed class AppRoute(val path: String) : Route { } @Serializable - data object Onramp : AppRoute(path = "/onramp") + data class Onramp(val currency: CryptoCurrency) : AppRoute(path = "/onramp/${currency.symbol}"), RouteBundleParams { + override fun getBundle(): Bundle = bundle(serializer()) + } @Serializable data class BuyCrypto( diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampAvailability.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampAvailability.kt new file mode 100644 index 0000000000..8113348d12 --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampAvailability.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.onramp.model + +sealed interface OnrampAvailability { + + data object Available : OnrampAvailability + data class NotSupported(val country: OnrampCountry) : OnrampAvailability + data class ConfirmResidency(val country: OnrampCountry) : OnrampAvailability +} \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt index 1a3a9b69dc..a430172a2e 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt @@ -1,5 +1,8 @@ package com.tangem.domain.onramp.model +import kotlinx.serialization.Serializable + +@Serializable data class OnrampCountry( val id: String, val name: String, diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt index 7f8fd42c20..4f3d44f222 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt @@ -1,5 +1,8 @@ package com.tangem.domain.onramp.model +import kotlinx.serialization.Serializable + +@Serializable data class OnrampCurrency( val name: String, val code: String, diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt new file mode 100644 index 0000000000..c2aa371b07 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampAvailability +import com.tangem.domain.onramp.repositories.OnrampRepository + +class CheckOnrampAvailabilityUseCase(private val repository: OnrampRepository) { + + suspend operator fun invoke(): Either { + return Either.catch { + return@catch OnrampAvailability.Available + repository.getDefaultCountrySync()?.let { savedCountry -> + return@catch if (savedCountry.onrampAvailable) { + OnrampAvailability.Available + } else { + OnrampAvailability.NotSupported(savedCountry) + } + } + + val detectedCountry = repository.getCountryByIp() + OnrampAvailability.ConfirmResidency(detectedCountry) + } + } +} \ No newline at end of file diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt index 16ac6b24f7..219536ddb7 100644 --- a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt @@ -5,7 +5,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent interface OnrampComponent : ComposableContentComponent { - class Params + data class Params(val currency: String) interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt index bae3a5be6e..dd8bd0a162 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt @@ -2,15 +2,11 @@ package com.tangem.features.onramp.confirmresidency import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.domain.onramp.model.OnrampCountry internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent { - data class Params( - val countryName: String, - val isOnrampSupported: Boolean, - val countryFlagUrl: String, - val onDismiss: () -> Unit, - ) + data class Params(val country: OnrampCountry, val onDismiss: (OnrampCountry) -> Unit) interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt index fa72ff4f67..25e5bce171 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt @@ -38,7 +38,7 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor( ) override fun dismiss() { - params.onDismiss() + router.pop() } @Composable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt index 44a4678bbe..2b0f91f3d1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt @@ -5,18 +5,24 @@ import com.arkivanov.decompose.router.slot.activate import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.onramp.GetOnrampCountryUseCase import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.entity.ConfirmResidencyBottomSheetConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import javax.inject.Inject @ComponentScoped internal class ConfirmResidencyModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + getOnrampCountryUseCase: GetOnrampCountryUseCase, paramsContainer: ParamsContainer, ) : Model() { @@ -24,9 +30,9 @@ internal class ConfirmResidencyModel @Inject constructor( val bottomSheetNavigation: SlotNavigation = SlotNavigation() val state: MutableStateFlow = MutableStateFlow( ConfirmResidencyUM( - country = params.countryName, - countryFlagUrl = params.countryFlagUrl, - isCountrySupported = params.isOnrampSupported, + country = params.country.name, + countryFlagUrl = params.country.image, + isCountrySupported = params.country.onrampAvailable, primaryButtonConfig = getPrimaryButtonConfig(), secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig( onClick = ::onChangeClick, @@ -35,22 +41,29 @@ internal class ConfirmResidencyModel @Inject constructor( ), ) - private fun getPrimaryButtonConfig() = if (params.isOnrampSupported) { + init { + getOnrampCountryUseCase.invoke() + .onEach { maybeCountry -> + val country = maybeCountry.getOrNull() + if (country != null) { + params.onDismiss(country) + } + } + .launchIn(modelScope) + } + + private fun getPrimaryButtonConfig() = if (params.country.onrampAvailable) { ConfirmResidencyUM.ActionButtonConfig( - onClick = params.onDismiss, + onClick = { params.onDismiss(params.country) }, text = resourceReference(R.string.common_confirm), ) } else { ConfirmResidencyUM.ActionButtonConfig( - onClick = ::onCloseClick, + onClick = router::pop, text = resourceReference(R.string.common_close), ) } - @Suppress("EmptyFunctionBlock") - private fun onCloseClick() { - } - private fun onChangeClick() { bottomSheetNavigation.activate(ConfirmResidencyBottomSheetConfig.SelectCountry) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt new file mode 100644 index 0000000000..be1d4dcb81 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt @@ -0,0 +1,66 @@ +package com.tangem.features.onramp.main + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent +import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig +import com.tangem.features.onramp.main.model.OnrampMainComponentModel +import com.tangem.features.onramp.main.ui.OnrampMainComponentContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnrampMainComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: OnrampMainComponent.Params, + private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory, +) : OnrampMainComponent, AppComponentContext by appComponentContext { + + private val model: OnrampMainComponentModel = getOrCreateModel(params) + private val bottomSheetSlot = childSlot( + source = model.bottomSheetNavigation, + serializer = null, + handleBackButton = false, + childFactory = ::bottomSheetChild, + ) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsState() + val bottomSheet by bottomSheetSlot.subscribeAsState() + + OnrampMainComponentContent(modifier = modifier, state = state) + bottomSheet.child?.instance?.BottomSheet() + } + + private fun bottomSheetChild( + config: OnrampMainBottomSheetConfig, + componentContext: ComponentContext, + ): ComposableBottomSheetComponent = when (config) { + is OnrampMainBottomSheetConfig.ConfirmResidency -> confirmResidencyComponentFactory.create( + context = childByContext(componentContext), + params = ConfirmResidencyComponent.Params( + country = config.country, + onDismiss = { model.bottomSheetNavigation.dismiss() }, + ), + ) + } + + @AssistedFactory + interface Factory : OnrampMainComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnrampMainComponent.Params, + ): DefaultOnrampMainComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt new file mode 100644 index 0000000000..22592a4268 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt @@ -0,0 +1,11 @@ +package com.tangem.features.onramp.main + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +internal interface OnrampMainComponent : ComposableContentComponent { + + class Params(val currency: String, val openSettings: () -> Unit) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt new file mode 100644 index 0000000000..99826bae5d --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.main.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.main.model.OnrampMainComponentModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampMainComponentModelModule { + + @Binds + @IntoMap + @ClassKey(OnrampMainComponentModel::class) + fun bindOnrampSelectCountryModel(model: OnrampMainComponentModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModule.kt new file mode 100644 index 0000000000..78140491e5 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModule.kt @@ -0,0 +1,18 @@ +package com.tangem.features.onramp.main.di + +import com.tangem.features.onramp.main.DefaultOnrampMainComponent +import com.tangem.features.onramp.main.OnrampMainComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampMainComponentModule { + + @Binds + @Singleton + fun bindOnrampMainComponentFactory(factory: DefaultOnrampMainComponent.Factory): OnrampMainComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt new file mode 100644 index 0000000000..03c2357074 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onramp.main.entity + +import com.tangem.domain.onramp.model.OnrampCountry +import kotlinx.serialization.Serializable + +@Serializable +internal sealed interface OnrampMainBottomSheetConfig { + @Serializable + data class ConfirmResidency(val country: OnrampCountry) : OnrampMainBottomSheetConfig +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt new file mode 100644 index 0000000000..91f5ee356e --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt @@ -0,0 +1,54 @@ +package com.tangem.features.onramp.main.entity + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.combinedReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.onramp.impl.R + +@Immutable +internal sealed interface OnrampMainComponentUM { + + val topBarConfig: OnrampMainTopBarUM + val buyButtonConfig: BuyButtonConfig + + data class InitialLoading( + val currency: String, + val onClose: () -> Unit, + val openSettings: () -> Unit, + val onBuyClick: () -> Unit, + ) : OnrampMainComponentUM { + override val topBarConfig: OnrampMainTopBarUM = OnrampMainTopBarUM( + title = combinedReference(resourceReference(R.string.common_buy), stringReference(" $currency")), + startButtonUM = TopAppBarButtonUM( + iconRes = R.drawable.ic_close_24, + onIconClicked = onClose, + enabled = true, + ), + endButtonUM = TopAppBarButtonUM( + iconRes = R.drawable.ic_more_vertical_24, + onIconClicked = openSettings, + enabled = false, + ), + ) + + override val buyButtonConfig: BuyButtonConfig = BuyButtonConfig( + text = resourceReference(R.string.common_buy), + onClick = onBuyClick, + enabled = false, + ) + } + + data class Content( + override val topBarConfig: OnrampMainTopBarUM, + override val buyButtonConfig: BuyButtonConfig, + ) : OnrampMainComponentUM +} + +internal data class BuyButtonConfig( + val text: TextReference, + val onClick: () -> Unit, + val enabled: Boolean, +) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainTopBarUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainTopBarUM.kt new file mode 100644 index 0000000000..997292dfb8 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainTopBarUM.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onramp.main.entity + +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.extensions.TextReference + +data class OnrampMainTopBarUM( + val title: TextReference, + val startButtonUM: TopAppBarButtonUM, + val endButtonUM: TopAppBarButtonUM, +) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/transformer/OnrampAvailabilityTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/transformer/OnrampAvailabilityTransformer.kt new file mode 100644 index 0000000000..9da2af4521 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/transformer/OnrampAvailabilityTransformer.kt @@ -0,0 +1,53 @@ +package com.tangem.features.onramp.main.entity.transformer + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampAvailability +import com.tangem.domain.onramp.model.OnrampCountry +import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig +import com.tangem.features.onramp.main.entity.OnrampMainComponentUM +import com.tangem.utils.transformer.Transformer +import timber.log.Timber + +internal class OnrampAvailabilityTransformer( + private val maybeAvailabilityStatus: Either, + private val openBottomSheet: (OnrampMainBottomSheetConfig) -> Unit, +) : Transformer { + + override fun transform(prevState: OnrampMainComponentUM): OnrampMainComponentUM { + return maybeAvailabilityStatus.fold( + ifLeft = { + Timber.e(it) + prevState + }, + ifRight = { availability -> + when (availability) { + OnrampAvailability.Available -> handleAvailableStatus(prevState) + is OnrampAvailability.ConfirmResidency -> handleShowingConfirmResidency( + prevState = prevState, + country = availability.country, + ) + is OnrampAvailability.NotSupported -> handleShowingConfirmResidency( + prevState = prevState, + country = availability.country, + ) + } + }, + ) + } + + private fun handleAvailableStatus(prevState: OnrampMainComponentUM): OnrampMainComponentUM { + val endButton = prevState.topBarConfig.endButtonUM.copy(enabled = true) + return OnrampMainComponentUM.Content( + topBarConfig = prevState.topBarConfig.copy(endButtonUM = endButton), + buyButtonConfig = prevState.buyButtonConfig, + ) + } + + private fun handleShowingConfirmResidency( + prevState: OnrampMainComponentUM, + country: OnrampCountry, + ): OnrampMainComponentUM { + openBottomSheet.invoke(OnrampMainBottomSheetConfig.ConfirmResidency(country)) + return prevState + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt new file mode 100644 index 0000000000..97f7c33854 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -0,0 +1,65 @@ +package com.tangem.features.onramp.main.model + +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.domain.onramp.CheckOnrampAvailabilityUseCase +import com.tangem.features.onramp.main.OnrampMainComponent +import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig +import com.tangem.features.onramp.main.entity.OnrampMainComponentUM +import com.tangem.features.onramp.main.entity.transformer.OnrampAvailabilityTransformer +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.transformer.Transformer +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +internal class OnrampMainComponentModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val checkOnrampAvailabilityUseCase: CheckOnrampAvailabilityUseCase, + paramsContainer: ParamsContainer, +) : Model() { + + private val params: OnrampMainComponent.Params = paramsContainer.require() + private val _state: MutableStateFlow = MutableStateFlow( + value = OnrampMainComponentUM.InitialLoading( + currency = params.currency, + onClose = router::pop, + openSettings = params.openSettings, + onBuyClick = ::onBuyClick, + ), + ) + val state: StateFlow get() = _state.asStateFlow() + val bottomSheetNavigation: SlotNavigation = SlotNavigation() + + init { + checkResidenceCountry() + } + + private fun checkResidenceCountry() { + modelScope.launch { + OnrampAvailabilityTransformer( + maybeAvailabilityStatus = checkOnrampAvailabilityUseCase.invoke(), + openBottomSheet = ::openBottomSheet, + ).let(::updateState) + } + } + + @Suppress("EmptyFunctionBlock") + private fun onBuyClick() { + } + + private fun openBottomSheet(config: OnrampMainBottomSheetConfig) { + bottomSheetNavigation.activate(config) + } + + private fun updateState(transformer: Transformer) { + _state.update(transformer::transform) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt new file mode 100644 index 0000000000..bf30582c07 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt @@ -0,0 +1,100 @@ +package com.tangem.features.onramp.main.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.FabPosition +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.WindowInsetsZero +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.main.entity.OnrampMainComponentUM + +@Composable +internal fun OnrampMainComponentContent(state: OnrampMainComponentUM, modifier: Modifier = Modifier) { + Scaffold( + modifier = modifier, + contentWindowInsets = WindowInsetsZero, + containerColor = TangemTheme.colors.background.secondary, + topBar = { + TangemTopAppBar( + modifier = Modifier.statusBarsPadding(), + startButton = state.topBarConfig.startButtonUM, + endButton = state.topBarConfig.endButtonUM, + title = state.topBarConfig.title.resolveReference(), + ) + }, + content = { innerPadding -> + val contentModifier = Modifier + .padding(innerPadding) + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth() + .wrapContentHeight() + when (state) { + is OnrampMainComponentUM.InitialLoading -> InitialLoading(modifier = contentModifier) + is OnrampMainComponentUM.Content -> Content(modifier = contentModifier) + } + }, + floatingActionButton = { + BuyButton( + modifier = Modifier + .navigationBarsPadding() + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + onClick = state.buyButtonConfig.onClick, + enabled = state.buyButtonConfig.enabled, + ) + }, + floatingActionButtonPosition = FabPosition.Center, + ) +} + +@Composable +private fun InitialLoading(modifier: Modifier = Modifier) { + Column( + modifier = modifier + .clip(shape = RoundedCornerShape(size = TangemTheme.dimens.radius16)) + .background(TangemTheme.colors.background.action) + .padding(vertical = TangemTheme.dimens.spacing28), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + CircleShimmer(modifier = Modifier.size(TangemTheme.dimens.size40)) + RectangleShimmer( + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing16) + .size(width = TangemTheme.dimens.size96, height = TangemTheme.dimens.size24), + radius = TangemTheme.dimens.radius3, + ) + RectangleShimmer( + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing16) + .size(width = TangemTheme.dimens.size72, height = TangemTheme.dimens.size12), + radius = TangemTheme.dimens.radius3, + ) + } +} + +@Composable +private fun Content(modifier: Modifier = Modifier) { + Box(modifier = modifier) +} + +@Composable +private fun BuyButton(onClick: () -> Unit, enabled: Boolean, modifier: Modifier = Modifier) { + PrimaryButton( + modifier = modifier, + text = stringResource(id = R.string.common_buy), + onClick = onClick, + enabled = enabled, + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt index c65a434c7a..16d046651d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt @@ -1,5 +1,6 @@ package com.tangem.features.onramp.root +import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -9,10 +10,13 @@ import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackA import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.push import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.onramp.component.OnrampComponent +import com.tangem.features.onramp.main.OnrampMainComponent import com.tangem.features.onramp.root.entity.OnrampChild import com.tangem.features.onramp.settings.OnrampSettingsComponent import dagger.assisted.Assisted @@ -22,8 +26,9 @@ import dagger.assisted.AssistedInject @Suppress("UnusedPrivateMember") internal class DefaultOnrampComponent @AssistedInject constructor( @Assisted context: AppComponentContext, - @Assisted params: OnrampComponent.Params, + @Assisted private val params: OnrampComponent.Params, private val settingsComponentFactory: OnrampSettingsComponent.Factory, + private val onrampMainComponentFactory: OnrampMainComponent.Factory, ) : OnrampComponent, AppComponentContext by context { private val navigation = StackNavigation() @@ -31,7 +36,7 @@ internal class DefaultOnrampComponent @AssistedInject constructor( key = "onramp_content_stack", source = navigation, serializer = OnrampChild.serializer(), - initialConfiguration = OnrampChild.Settings, + initialConfiguration = OnrampChild.Main, handleBackButton = false, childFactory = ::screenChild, ) @@ -40,6 +45,7 @@ internal class DefaultOnrampComponent @AssistedInject constructor( override fun Content(modifier: Modifier) { val childStack by contentStack.subscribeAsState() + BackHandler(onBack = router::pop) Children( stack = childStack, animation = stackAnimation(), @@ -52,10 +58,15 @@ internal class DefaultOnrampComponent @AssistedInject constructor( when (config) { OnrampChild.Settings -> settingsComponentFactory.create( context = childByContext(componentContext), - // params = OnrampSettingsComponent.Params(navigation::pop) // todo uncomment after [REDACTED_JIRA] - params = OnrampSettingsComponent.Params(router::pop), + params = OnrampSettingsComponent.Params(navigation::pop), + ) + OnrampChild.Main -> onrampMainComponentFactory.create( + context = childByContext(componentContext), + params = OnrampMainComponent.Params( + currency = params.currency, + openSettings = { navigation.push(OnrampChild.Settings) }, + ), ) - OnrampChild.Main -> TODO("[REDACTED_JIRA]") } @AssistedFactory From d2c40cfd5c6f77c5f736fa22aa0022ce5a6f7324 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Nov 2024 20:37:51 +0700 Subject: [PATCH 31/33] Updated on 2026-08-14 --- app/src/main/assets/testnet_tokens.json | 10 ++++++++++ .../mercuryo/MercuryoBlockchainMapping.kt | 2 ++ .../moonpay/MoonpayBlockchainMapping.kt | 2 ++ .../core/ui/extensions/BlockchainIcons.kt | 3 +++ .../ui/src/main/res/drawable/ic_chiliz_22.xml | 9 +++++++++ .../src/main/res/drawable/img_chiliz_22.xml | 20 +++++++++++++++++++ .../feature/swap/domain/SwapInteractorImpl.kt | 2 ++ gradle/dependencies.toml | 2 +- .../tangem/blockchainsdk/utils/Blockchain.kt | 5 +++++ 9 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 core/ui/src/main/res/drawable/ic_chiliz_22.xml create mode 100644 core/ui/src/main/res/drawable/img_chiliz_22.xml diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index 30fa60f3c4..67b96dd4a9 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -749,6 +749,16 @@ } ] }, + { + "id": "chiliz", + "name": "Chiliz", + "symbol": "CHZ", + "networks": [ + { + "networkId": "chiliz/test" + } + ] + }, { "id": "casper-network", "name": "Casper", diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt index 946d2afce1..9a930fdf76 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt @@ -138,6 +138,8 @@ internal val Blockchain.mercuryoNetwork: String? Blockchain.CasperTestnet -> null Blockchain.Core -> null Blockchain.CoreTestnet -> null + Blockchain.Chiliz -> null + Blockchain.ChilizTestnet -> null Blockchain.Unknown -> null Blockchain.Xodex -> null Blockchain.Canxium -> null diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt index fb01fff927..bce26d7a6a 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt @@ -43,6 +43,7 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency? Filecoin -> MoonPaySupportedCurrency(networkCode = "filecoin", currencyCode = "fil") Sui -> MoonPaySupportedCurrency(networkCode = "sui", currencyCode = "sui") Core -> MoonPaySupportedCurrency(networkCode = "core", currencyCode = "core") + Chiliz -> MoonPaySupportedCurrency(networkCode = "ethereum", currencyCode = "chz") ArbitrumTestnet -> null AvalancheTestnet -> null BinanceTestnet -> null @@ -139,6 +140,7 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency? Casper -> null CasperTestnet -> null CoreTestnet -> null + ChilizTestnet -> null Unknown -> null Xodex -> null Canxium -> null diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index 28de4a0d24..5aa40111d8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -82,6 +82,7 @@ fun getActiveIconRes(blockchainId: String): Int { "casper", "casper/test" -> R.drawable.img_casper_22 "xodex" -> R.drawable.img_xodex_22 "canxium" -> R.drawable.img_canxium_22 + "chiliz", "chiliz/test" -> R.drawable.img_chiliz_22 else -> R.drawable.ic_alert_24 } } @@ -162,6 +163,7 @@ fun getActiveIconResByCoinId(coinId: String): Int { "casper-network" -> R.drawable.img_casper_22 "xodex" -> R.drawable.img_xodex_22 "canxium" -> R.drawable.img_canxium_22 + "chiliz", "chiliz/test" -> R.drawable.img_chiliz_22 else -> R.drawable.ic_alert_24 } } @@ -245,6 +247,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "casper", "casper/test" -> R.drawable.ic_casper_22 "xodex" -> R.drawable.ic_xodex_22 "canxium" -> R.drawable.ic_canxium_22 + "chiliz", "chiliz/test" -> R.drawable.ic_chiliz_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_chiliz_22.xml b/core/ui/src/main/res/drawable/ic_chiliz_22.xml new file mode 100644 index 0000000000..8d1f446943 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_chiliz_22.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/img_chiliz_22.xml b/core/ui/src/main/res/drawable/img_chiliz_22.xml new file mode 100644 index 0000000000..70f61bbde0 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_chiliz_22.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 77aa2d704c..436f3cbc1e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1045,6 +1045,8 @@ internal class SwapInteractorImpl @AssistedInject constructor( Blockchain.CasperTestnet, Blockchain.Core, Blockchain.CoreTestnet, + Blockchain.Chiliz, + Blockchain.ChilizTestnet, Blockchain.Xodex, Blockchain.Canxium, -> Fee.Common(feeAmount) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 5988269248..5d31f72ead 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-848" +tangemBlockchainSdk = "develop-860" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-397" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index 41bc95a6f8..a9830d7036 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -138,6 +138,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "core/test" -> Blockchain.CoreTestnet "casper-network" -> Blockchain.Casper "casper-network/test" -> Blockchain.CasperTestnet + "chiliz" -> Blockchain.Chiliz + "chiliz/test" -> Blockchain.ChilizTestnet "xodex" -> Blockchain.Xodex "canxium" -> Blockchain.Canxium else -> null @@ -277,6 +279,8 @@ fun Blockchain.toNetworkId(): String { Blockchain.CasperTestnet -> "casper-network/test" Blockchain.Core -> "core" Blockchain.CoreTestnet -> "core/test" + Blockchain.Chiliz -> "chiliz" + Blockchain.ChilizTestnet -> "chiliz/test" Blockchain.Xodex -> "xodex" Blockchain.Canxium -> "canxium" } @@ -368,6 +372,7 @@ fun Blockchain.toCoinId(): String { Blockchain.EnergyWebX, Blockchain.EnergyWebXTestnet -> "energy-web-token" Blockchain.Casper, Blockchain.CasperTestnet -> "casper-network" Blockchain.Core, Blockchain.CoreTestnet -> "coredaoorg" + Blockchain.Chiliz, Blockchain.ChilizTestnet -> "chiliz" Blockchain.Xodex -> "xodex" Blockchain.Canxium -> "canxium" } From c915a56e61a2962654ed4dc236c87e54d17984b2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 8 Nov 2024 18:04:36 +0500 Subject: [PATCH 32/33] Updated on 2026-08-14 --- .../state/StakingStateController.kt | 2 +- .../state/events/StakingAlertUM.kt | 8 +++++ .../state/helpers/StakingTransactionSender.kt | 36 ++++++++++++++----- ...tConfirmationStateInProgressTransformer.kt | 2 -- .../viewmodel/StakingViewModel.kt | 22 ++++++++++-- 5 files changed, 55 insertions(+), 15 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt index 71d3c67900..c30d7b9185 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt @@ -63,7 +63,7 @@ internal class StakingStateController @Inject constructor( } } - private fun dismissAlert() { + fun dismissAlert() { mutableUiState.update { it.copy(event = consumedEvent()) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingAlertUM.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingAlertUM.kt index a00130dcab..3d384541bd 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingAlertUM.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingAlertUM.kt @@ -33,4 +33,12 @@ internal sealed class StakingAlertUM : AlertUM { override val confirmButtonText = resourceReference(R.string.common_ok) override val onConfirmClick = null } + + data class FeeIncreased( + override val onConfirmClick: () -> Unit, + ) : StakingAlertUM() { + override val title: TextReference? = null + override val message: TextReference = resourceReference(id = R.string.send_notification_high_fee_title) + override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok) + } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index 3ccad868cc..09b6415c41 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -4,9 +4,14 @@ import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.domain.staking.* +import com.tangem.domain.staking.GetConstructedStakingTransactionUseCase +import com.tangem.domain.staking.GetStakingTransactionUseCase +import com.tangem.domain.staking.SaveUnsubmittedHashUseCase +import com.tangem.domain.staking.SubmitHashUseCase import com.tangem.domain.staking.model.SubmitHashData -import com.tangem.domain.staking.model.stakekit.* +import com.tangem.domain.staking.model.stakekit.PendingAction +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction @@ -18,7 +23,10 @@ import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.models.UserWallet -import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.features.staking.impl.presentation.state.FeeState +import com.tangem.features.staking.impl.presentation.state.StakingStateController +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions import com.tangem.utils.extensions.orZero @@ -55,6 +63,7 @@ internal class StakingTransactionSender @AssistedInject constructor( onConstructError: (StakingError) -> Unit, onSendSuccess: (String) -> Unit, onSendError: (SendTransactionError?) -> Unit, + onFeeIncreased: (Fee) -> Unit, ) { val state = stateController.value @@ -82,13 +91,22 @@ internal class StakingTransactionSender @AssistedInject constructor( return } - onConstructSuccess(fullTransactionsData.map { it.stakeKitTransaction }) + val totalFee = fullTransactionsData.sumOf { it.stakeKitTransaction.gasEstimate?.amount.orZero() } - sendStakingTransaction( - fullTransactionsData = fullTransactionsData, - onSendSuccess = onSendSuccess, - onSendError = onSendError, - ) + if (fee.amount.value.orZero() >= totalFee) { + onConstructSuccess(fullTransactionsData.map { it.stakeKitTransaction }) + sendStakingTransaction( + fullTransactionsData = fullTransactionsData, + onSendSuccess = onSendSuccess, + onSendError = onSendError, + ) + } else { + onFeeIncreased( + Fee.Common( + fee.amount.copy(value = totalFee), + ), + ) + } } private suspend fun getStakingTransactions( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt index 9043cc8ae9..c35018c9e7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt @@ -1,6 +1,5 @@ package com.tangem.features.staking.impl.presentation.state.transformers -import com.tangem.core.ui.extensions.TextReference import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.StakingUiState @@ -19,7 +18,6 @@ internal class SetConfirmationStateInProgressTransformer : Transformer + stateController.updateAll( + SetConfirmationStateResetAssentTransformer, + SetConfirmationStateAssentTransformer( + appCurrencyProvider = Provider { appCurrency }, + feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, + fee = increasedFee, + ), + ) + stateController.updateEvent( + StakingEvent.ShowAlert( + StakingAlertUM.FeeIncreased(stateController::dismissAlert), + ), + ) + updateNotifications() + }, ) }.saveIn(sendTransactionJobHolder) } From d220478cebaeb24dab03f2d2e0cb7bf30cd92cd3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Nov 2024 11:19:00 +0200 Subject: [PATCH 33/33] Updated on 2026-08-14 --- .../tangem/tap/features/onboarding/OnboardingHelper.kt | 9 ++++++--- .../products/wallet/redux/OnboardingWalletMiddleware.kt | 8 +++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 7ee5761836..4fc3109010 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -16,6 +16,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.builder.UserWalletIdBuilder +import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding @@ -91,6 +92,7 @@ object OnboardingHelper { } fun saveWallet( + alreadyCreatedWallet: UserWallet?, scanResponse: ScanResponse, accessCode: String? = null, backupCardsIds: List? = null, @@ -120,7 +122,7 @@ object OnboardingHelper { // When should not save user wallets but device has biometry and save wallet screen has not been shown, // then open save wallet screen tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> { - proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError) + proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet) delay(timeMillis = 1_200) @@ -134,7 +136,7 @@ object OnboardingHelper { } // If device has no biometry and save wallet screen has been shown, then go through old scenario else -> { - proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError) + proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet) } } } @@ -245,9 +247,10 @@ object OnboardingHelper { scanResponse: ScanResponse, backupCardsIds: List?, hasBackupError: Boolean, + alreadyCreatedWallet: UserWallet? = null, ) { val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase) - val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase) + val userWallet = alreadyCreatedWallet ?: UserWalletBuilder(scanResponse, walletNameGenerateUseCase) .hasBackupError(hasBackupError) .backupCardsIds(backupCardsIds?.toSet()) .build() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index dc9e77a48d..d306ced544 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -196,11 +196,11 @@ private fun handleWalletAction(action: Action) { } } -private fun handleFinishBackup(scanResponse: ScanResponse) { +private fun handleFinishBackup(scanResponse: ScanResponse, userWallet: UserWallet? = null) { val backupState = store.state.onboardingWalletState.backupState val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState) - val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() + val userWalletId = userWallet?.walletId ?: UserWalletIdBuilder.scanResponse(scanResponse).build() if (backupState.hasRing && userWalletId != null) { scope.launch { store.inject(DaggerGraphState::walletsRepository).setHasWalletsWithRing(userWalletId = userWalletId) @@ -208,6 +208,7 @@ private fun handleFinishBackup(scanResponse: ScanResponse) { } OnboardingHelper.saveWallet( + alreadyCreatedWallet = userWallet, scanResponse = updatedScanResponse, accessCode = backupState.accessCode, backupCardsIds = backupState.backupCardIds, @@ -667,7 +668,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) Analytics.send(Onboarding.Finished()) store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds) - handleFinishBackup(requireNotNull(scanResponse)) + handleFinishBackup(requireNotNull(scanResponse), userWallet) delay(1000) store.dispatchWithMain(BackupAction.BackupFinished(userWalletId = userWallet?.walletId)) } @@ -728,6 +729,7 @@ private suspend fun createUserWallet(scanResponse: ScanResponse, backupState: Ba return requireNotNull( value = UserWalletBuilder(scanResponse, walletNameGenerateUseCase) .backupCardsIds(backupState.backupCardIds.toSet()) + .hasBackupError(backupState.hasBackupError) .build(), lazyMessage = { "User wallet not created" }, )