From 902d80d1f1da55d865100f0fa6cc33edec824a1a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 19:07:02 +0400 Subject: [PATCH 01/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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/99] 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" }, ) From 3bac111c2b29886e2b3535da537d2790416fcd51 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Nov 2024 13:51:35 +0300 Subject: [PATCH 34/99] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index adbdabe422..17f16c2e69 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit adbdabe422b0513640d1750f276298683d342f61 +Subproject commit 17f16c2e698238e2b79c2360d4f80b2caa8583a8 From 854b04624db1969f95f0b7b2c4e49a94d9bdaa8e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Nov 2024 17:12:02 +0500 Subject: [PATCH 35/99] Updated on 2026-08-14 --- .../tap/di/domain/OnrampDomainModule.kt | 6 + .../tangem/tap/routing/utils/ChildFactory.kt | 2 +- .../domain/onramp/model/OnrampAvailability.kt | 8 +- .../onramp/CheckOnrampAvailabilityUseCase.kt | 7 +- .../domain/onramp/GetOnrampCurrencyUseCase.kt | 19 +++ features/onramp/api/build.gradle.kts | 1 + .../onramp/component/OnrampComponent.kt | 3 +- .../DefaultConfirmResidencyComponent.kt | 2 +- .../ConfirmResidencyBottomSheetConfig.kt | 2 +- .../model/ConfirmResidencyModel.kt | 2 +- .../onramp/main/DefaultOnrampMainComponent.kt | 6 + .../onramp/main/OnrampMainComponent.kt | 3 +- .../onramp/main/entity/AmountBlockState.kt | 20 +++ .../onramp/main/entity/OnrampIntents.kt | 8 + .../entity/OnrampMainBottomSheetConfig.kt | 3 + .../main/entity/OnrampMainComponentUM.kt | 1 + .../main/entity/factory/OnrampStateFactory.kt | 85 ++++++++++ .../OnrampAmountFieldChangeConverter.kt | 63 ++++++++ .../amount/OnrampAmountStateFactory.kt | 38 +++++ .../OnrampAvailabilityTransformer.kt | 53 ------- .../main/model/OnrampMainComponentModel.kt | 79 +++++++--- .../onramp/main/ui/OnrampAmountContent.kt | 146 ++++++++++++++++++ .../main/ui/OnrampMainComponentContent.kt | 17 +- .../onramp/root/DefaultOnrampComponent.kt | 2 +- 24 files changed, 486 insertions(+), 90 deletions(-) create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrencyUseCase.kt rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/{ => confirmresidency}/entity/ConfirmResidencyBottomSheetConfig.kt (77%) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt delete 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/ui/OnrampAmountContent.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 f15fd1b0c0..81e25125ae 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 @@ -47,4 +47,10 @@ internal object OnrampDomainModule { fun provideCheckOnrampAvailabilityUseCase(onrampRepository: OnrampRepository): CheckOnrampAvailabilityUseCase { return CheckOnrampAvailabilityUseCase(onrampRepository) } + + @Provides + @Singleton + fun provideGetOnrampCurrencyUseCase(onrampRepository: OnrampRepository): GetOnrampCurrencyUseCase { + return GetOnrampCurrencyUseCase(onrampRepository) + } } \ No newline at end of file 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 8678616017..48042171a4 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(route.currency.name), + params = OnrampComponent.Params(route.currency), componentFactory = onrampComponentFactory, ) } 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 index 8113348d12..601907de1a 100644 --- 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 @@ -2,7 +2,9 @@ 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 + val country: OnrampCountry + + data class Available(override val country: OnrampCountry, val currency: OnrampCurrency) : OnrampAvailability + data class NotSupported(override val country: OnrampCountry) : OnrampAvailability + data class ConfirmResidency(override val country: OnrampCountry) : OnrampAvailability } \ No newline at end of file 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 index c2aa371b07..46af00376b 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt @@ -8,10 +8,13 @@ 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 + val currency = repository.getDefaultCurrencySync() ?: run { + repository.saveDefaultCurrency(savedCountry.defaultCurrency) + savedCountry.defaultCurrency + } + OnrampAvailability.Available(country = savedCountry, currency = currency) } else { OnrampAvailability.NotSupported(savedCountry) } diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrencyUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrencyUseCase.kt new file mode 100644 index 0000000000..d2fc5147d1 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrencyUseCase.kt @@ -0,0 +1,19 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.domain.onramp.repositories.OnrampRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map + +class GetOnrampCurrencyUseCase(private val repository: OnrampRepository) { + + operator fun invoke(): Flow> { + return repository.getDefaultCurrency() + .map> { it.right() } + .catch { emit(it.left()) } + } +} \ No newline at end of file diff --git a/features/onramp/api/build.gradle.kts b/features/onramp/api/build.gradle.kts index cde16c9b92..632fd29071 100644 --- a/features/onramp/api/build.gradle.kts +++ b/features/onramp/api/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { implementation(projects.core.ui) /* Project - Domain */ + implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) /* Compose */ 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 219536ddb7..52be1340e5 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 @@ -2,10 +2,11 @@ package com.tangem.features.onramp.component import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.tokens.model.CryptoCurrency interface OnrampComponent : ComposableContentComponent { - data class Params(val currency: String) + data class Params(val cryptoCurrency: CryptoCurrency) 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 25e5bce171..fe1f307998 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 @@ -17,7 +17,7 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.features.onramp.confirmresidency.model.ConfirmResidencyModel 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.confirmresidency.entity.ConfirmResidencyBottomSheetConfig import com.tangem.features.onramp.selectcountry.SelectCountryComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/entity/ConfirmResidencyBottomSheetConfig.kt similarity index 77% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyBottomSheetConfig.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/entity/ConfirmResidencyBottomSheetConfig.kt index cccafaaa8d..baacb6b92a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyBottomSheetConfig.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/entity/ConfirmResidencyBottomSheetConfig.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.entity +package com.tangem.features.onramp.confirmresidency.entity import kotlinx.serialization.Serializable 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 2b0f91f3d1..25a7058831 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 @@ -11,7 +11,7 @@ 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.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn 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 index be1d4dcb81..8e4f11228c 100644 --- 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 @@ -16,6 +16,7 @@ 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 com.tangem.features.onramp.selectcurrency.SelectCurrencyComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -24,6 +25,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted params: OnrampMainComponent.Params, private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory, + private val selectCurrencyComponentFactory: SelectCurrencyComponent.Factory, ) : OnrampMainComponent, AppComponentContext by appComponentContext { private val model: OnrampMainComponentModel = getOrCreateModel(params) @@ -54,6 +56,10 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor( onDismiss = { model.bottomSheetNavigation.dismiss() }, ), ) + is OnrampMainBottomSheetConfig.CurrenciesList -> selectCurrencyComponentFactory.create( + context = childByContext(componentContext), + params = SelectCurrencyComponent.Params(model.bottomSheetNavigation::dismiss), + ) } @AssistedFactory 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 index 22592a4268..de585fef2d 100644 --- 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 @@ -2,10 +2,11 @@ package com.tangem.features.onramp.main import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.tokens.model.CryptoCurrency internal interface OnrampMainComponent : ComposableContentComponent { - class Params(val currency: String, val openSettings: () -> Unit) + data class Params(val cryptoCurrency: CryptoCurrency, 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/entity/AmountBlockState.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt new file mode 100644 index 0000000000..d0cfbe8515 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.main.entity + +import androidx.compose.runtime.Immutable +import com.tangem.common.ui.amountScreen.models.AmountFieldModel +import com.tangem.core.ui.extensions.TextReference + +internal data class OnrampAmountBlockUM( + val currencyUM: OnrampCurrencyUM, + val amountFieldModel: AmountFieldModel, + val secondaryFieldModel: OnrampAmountSecondaryFieldUM, +) + +internal data class OnrampCurrencyUM(val code: String, val iconUrl: String, val precision: Int, val onClick: () -> Unit) + +@Immutable +internal sealed interface OnrampAmountSecondaryFieldUM { + data object Loading : OnrampAmountSecondaryFieldUM + data class Content(val amount: TextReference) : OnrampAmountSecondaryFieldUM + data class Error(val error: TextReference) : OnrampAmountSecondaryFieldUM +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt new file mode 100644 index 0000000000..0b1002a3bd --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt @@ -0,0 +1,8 @@ +package com.tangem.features.onramp.main.entity + +interface OnrampIntents { + fun onAmountValueChanged(value: String) + fun openSettings() + fun openCurrenciesList() + fun onBuyClick() +} \ 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 index 03c2357074..397816b229 100644 --- 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 @@ -7,4 +7,7 @@ import kotlinx.serialization.Serializable internal sealed interface OnrampMainBottomSheetConfig { @Serializable data class ConfirmResidency(val country: OnrampCountry) : OnrampMainBottomSheetConfig + + @Serializable + data object CurrenciesList : 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 index 91f5ee356e..b909ee310e 100644 --- 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 @@ -44,6 +44,7 @@ internal sealed interface OnrampMainComponentUM { data class Content( override val topBarConfig: OnrampMainTopBarUM, override val buyButtonConfig: BuyButtonConfig, + val amountBlockState: OnrampAmountBlockUM, ) : OnrampMainComponentUM } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt new file mode 100644 index 0000000000..6c40dafb39 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt @@ -0,0 +1,85 @@ +package com.tangem.features.onramp.main.entity.factory + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.ui.amountScreen.models.AmountFieldModel +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.domain.tokens.model.Amount +import com.tangem.domain.tokens.model.AmountType +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.convertToAmount +import com.tangem.features.onramp.main.entity.* +import com.tangem.features.onramp.main.entity.OnrampAmountBlockUM +import com.tangem.features.onramp.main.entity.OnrampAmountSecondaryFieldUM +import com.tangem.features.onramp.main.entity.OnrampCurrencyUM +import com.tangem.features.onramp.main.entity.OnrampMainComponentUM +import com.tangem.utils.Provider +import java.math.BigDecimal + +internal class OnrampStateFactory( + private val currentStateProvider: Provider, + private val cryptoCurrency: CryptoCurrency, + private val onrampIntents: OnrampIntents, +) { + + fun getInitialState(currency: String, onClose: () -> Unit): OnrampMainComponentUM.InitialLoading { + return OnrampMainComponentUM.InitialLoading( + currency = currency, + onClose = onClose, + openSettings = onrampIntents::openSettings, + onBuyClick = onrampIntents::onBuyClick, + ) + } + + fun getReadyState(currency: OnrampCurrency): OnrampMainComponentUM.Content { + val state = currentStateProvider() + + val endButton = state.topBarConfig.endButtonUM.copy(enabled = true) + return OnrampMainComponentUM.Content( + topBarConfig = state.topBarConfig.copy(endButtonUM = endButton), + buyButtonConfig = state.buyButtonConfig, + amountBlockState = getInitialAmountBlockState(currency), + ) + } + + private fun getInitialAmountBlockState(currency: OnrampCurrency): OnrampAmountBlockUM { + return OnrampAmountBlockUM( + currencyUM = OnrampCurrencyUM( + code = currency.code, + iconUrl = currency.image, + precision = currency.precision, + onClick = onrampIntents::openCurrenciesList, + ), + amountFieldModel = AmountFieldModel( + value = "", + fiatValue = "", + onValueChange = onrampIntents::onAmountValueChanged, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.None, + keyboardType = KeyboardType.Number, + ), + keyboardActions = KeyboardActions(), + isFiatValue = true, + cryptoAmount = BigDecimal.ZERO.convertToAmount(cryptoCurrency), + fiatAmount = BigDecimal.ZERO.convertToFiatAmount(currency), + isError = false, + isWarning = false, + error = TextReference.EMPTY, + isFiatUnavailable = false, + isValuePasted = false, + onValuePastedTriggerDismiss = {}, + ), + secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading, + ) + } + + private fun BigDecimal.convertToFiatAmount(currency: OnrampCurrency): Amount = Amount( + currencySymbol = currency.name, + value = this, + decimals = currency.precision, + type = AmountType.FiatType(currency.code), + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt new file mode 100644 index 0000000000..8a8c93fef6 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt @@ -0,0 +1,63 @@ +package com.tangem.features.onramp.main.entity.factory.amount + +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.features.onramp.main.entity.OnrampAmountSecondaryFieldUM +import com.tangem.features.onramp.main.entity.OnrampMainComponentUM +import com.tangem.utils.Provider +import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero +import java.math.BigDecimal + +internal class OnrampAmountFieldChangeConverter( + private val currentStateProvider: Provider, +) : Converter { + + override fun convert(value: String): OnrampMainComponentUM { + val state = currentStateProvider() + if (state !is OnrampMainComponentUM.Content) return state + + if (value.isEmpty()) return state.emptyState() + + val amountState = state.amountBlockState + val amountTextField = amountState.amountFieldModel + val fiatDecimal = value.toBigDecimalOrNull() ?: BigDecimal.ZERO + val isDoneActionEnabled = !fiatDecimal.isNullOrZero() + val amountFieldModel = amountState.amountFieldModel.copy( + fiatValue = value, + fiatAmount = amountTextField.fiatAmount.copy(value = fiatDecimal), + keyboardOptions = KeyboardOptions( + imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, + keyboardType = KeyboardType.Number, + ), + ) + + return state.copy( + amountBlockState = amountState.copy( + amountFieldModel = amountFieldModel, + secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading, + ), + ) + } + + private fun OnrampMainComponentUM.Content.emptyState(): OnrampMainComponentUM.Content { + val amountFieldModel = amountBlockState.amountFieldModel.copy( + value = "", + fiatValue = "", + cryptoAmount = amountBlockState.amountFieldModel.cryptoAmount.copy(value = BigDecimal.ZERO), + fiatAmount = amountBlockState.amountFieldModel.fiatAmount.copy(value = BigDecimal.ZERO), + isError = false, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.None, + keyboardType = KeyboardType.Number, + ), + ) + return copy( + amountBlockState = amountBlockState.copy( + amountFieldModel = amountFieldModel, + secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading, + ), + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt new file mode 100644 index 0000000000..fd4c03914f --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt @@ -0,0 +1,38 @@ +package com.tangem.features.onramp.main.entity.factory.amount + +import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.domain.tokens.model.AmountType +import com.tangem.features.onramp.main.entity.OnrampMainComponentUM +import com.tangem.utils.Provider + +internal class OnrampAmountStateFactory(private val currentStateProvider: Provider) { + + private val onrampAmountFieldChangeConverter = OnrampAmountFieldChangeConverter( + currentStateProvider = currentStateProvider, + ) + + fun getOnAmountValueChange(value: String) = onrampAmountFieldChangeConverter.convert(value) + + fun getUpdatedCurrencyState(currency: OnrampCurrency): OnrampMainComponentUM { + val currentState = currentStateProvider() + if (currentState !is OnrampMainComponentUM.Content) return currentState + + val amountState = currentState.amountBlockState + return currentState.copy( + amountBlockState = amountState.copy( + currencyUM = amountState.currencyUM.copy( + code = currency.code, + iconUrl = currency.image, + precision = currency.precision, + ), + amountFieldModel = amountState.amountFieldModel.copy( + fiatAmount = amountState.amountFieldModel.fiatAmount.copy( + currencySymbol = currency.code, + decimals = currency.precision, + type = AmountType.FiatType(currency.code), + ), + ), + ), + ) + } +} \ 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 deleted file mode 100644 index 9da2af4521..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/transformer/OnrampAvailabilityTransformer.kt +++ /dev/null @@ -1,53 +0,0 @@ -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 index 97f7c33854..50834be67d 100644 --- 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 @@ -6,33 +6,42 @@ 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.domain.onramp.GetOnrampCurrencyUseCase +import com.tangem.domain.onramp.model.OnrampAvailability +import com.tangem.domain.onramp.model.OnrampCurrency import com.tangem.features.onramp.main.OnrampMainComponent +import com.tangem.features.onramp.main.entity.OnrampIntents 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.features.onramp.main.entity.factory.OnrampStateFactory +import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory +import com.tangem.utils.Provider 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.flow.* import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject internal class OnrampMainComponentModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val router: Router, private val checkOnrampAvailabilityUseCase: CheckOnrampAvailabilityUseCase, + private val getOnrampCurrencyUseCase: GetOnrampCurrencyUseCase, paramsContainer: ParamsContainer, -) : Model() { +) : Model(), OnrampIntents { private val params: OnrampMainComponent.Params = paramsContainer.require() + private val stateFactory = OnrampStateFactory( + currentStateProvider = Provider { _state.value }, + cryptoCurrency = params.cryptoCurrency, + onrampIntents = this, + ) + private val amountStateFactory = OnrampAmountStateFactory(currentStateProvider = Provider { _state.value }) + private val _state: MutableStateFlow = MutableStateFlow( - value = OnrampMainComponentUM.InitialLoading( - currency = params.currency, + value = stateFactory.getInitialState( + currency = params.cryptoCurrency.name, onClose = router::pop, - openSettings = params.openSettings, - onBuyClick = ::onBuyClick, ), ) val state: StateFlow get() = _state.asStateFlow() @@ -44,22 +53,50 @@ internal class OnrampMainComponentModel @Inject constructor( private fun checkResidenceCountry() { modelScope.launch { - OnrampAvailabilityTransformer( - maybeAvailabilityStatus = checkOnrampAvailabilityUseCase.invoke(), - openBottomSheet = ::openBottomSheet, - ).let(::updateState) + checkOnrampAvailabilityUseCase.invoke() + .onRight(::handleOnrampAvailability) + .onLeft { Timber.e(it) } } } - @Suppress("EmptyFunctionBlock") - private fun onBuyClick() { + private fun handleOnrampAvailability(availability: OnrampAvailability) { + when (availability) { + is OnrampAvailability.Available -> handleOnrampAvailable(availability.currency) + is OnrampAvailability.ConfirmResidency, + is OnrampAvailability.NotSupported, + -> bottomSheetNavigation.activate(OnrampMainBottomSheetConfig.ConfirmResidency(availability.country)) + } } - private fun openBottomSheet(config: OnrampMainBottomSheetConfig) { - bottomSheetNavigation.activate(config) + private fun handleOnrampAvailable(currency: OnrampCurrency) { + _state.update { stateFactory.getReadyState(currency) } + subscribeToCurrencyUpdates() } - private fun updateState(transformer: Transformer) { - _state.update(transformer::transform) + private fun subscribeToCurrencyUpdates() { + getOnrampCurrencyUseCase.invoke() + .onEach { maybeCurrency -> + val currency = maybeCurrency.getOrNull() + if (currency != null) { + _state.update { amountStateFactory.getUpdatedCurrencyState(currency) } + } + } + .launchIn(modelScope) + } + + override fun onAmountValueChanged(value: String) { + _state.update { amountStateFactory.getOnAmountValueChange(value) } + } + + override fun openSettings() { + params.openSettings() + } + + override fun onBuyClick() { + TODO("Not yet implemented") + } + + override fun openCurrenciesList() { + bottomSheetNavigation.activate(OnrampMainBottomSheetConfig.CurrenciesList) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt new file mode 100644 index 0000000000..b75e7d3976 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt @@ -0,0 +1,146 @@ +package com.tangem.features.onramp.main.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDirection +import coil.compose.AsyncImage +import com.tangem.common.ui.amountScreen.models.AmountFieldModel +import com.tangem.core.ui.components.TextShimmer +import com.tangem.core.ui.components.fields.AmountTextField +import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.rememberDecimalFormat +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.main.entity.OnrampAmountBlockUM +import com.tangem.features.onramp.main.entity.OnrampAmountSecondaryFieldUM +import com.tangem.features.onramp.main.entity.OnrampCurrencyUM +import kotlinx.coroutines.delay + +@Composable +internal fun OnrampAmountContent(state: OnrampAmountBlockUM, 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, + ) { + OnrampCurrencyIcon( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing24), + currencyUM = state.currencyUM, + ) + OnrampAmountField(amountField = state.amountFieldModel) + OnrampAmountSecondary(state = state.secondaryFieldModel) + } +} + +@Composable +private fun OnrampAmountField(amountField: AmountFieldModel) { + val decimalFormat = rememberDecimalFormat() + val requester = remember { FocusRequester() } + AmountTextField( + value = amountField.fiatValue, + decimals = amountField.fiatAmount.decimals, + visualTransformation = AmountVisualTransformation( + decimals = amountField.fiatAmount.decimals, + symbol = amountField.fiatAmount.currencySymbol, + decimalFormat = decimalFormat, + ), + onValueChange = amountField.onValueChange, + keyboardOptions = amountField.keyboardOptions, + keyboardActions = amountField.keyboardActions, + textStyle = TangemTheme.typography.h2.copy( + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ), + isEnabled = !amountField.isError, + isAutoResize = true, + isValuePasted = amountField.isValuePasted, + onValuePastedTriggerDismiss = amountField.onValuePastedTriggerDismiss, + modifier = Modifier + .focusRequester(requester) + .padding( + top = TangemTheme.dimens.spacing24, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + ) + .requiredHeightIn(min = TangemTheme.dimens.size32), + ) + + LaunchedEffect(key1 = Unit) { + delay(timeMillis = 200) + requester.requestFocus() + } +} + +@Composable +private fun OnrampAmountSecondary(state: OnrampAmountSecondaryFieldUM) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding( + top = TangemTheme.dimens.spacing8, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + ), + contentAlignment = Alignment.Center, + ) { + when (state) { + is OnrampAmountSecondaryFieldUM.Content -> Text( + text = state.amount.resolveReference(), + style = TangemTheme.typography.caption2.copy(textDirection = TextDirection.ContentOrLtr), + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + ) + is OnrampAmountSecondaryFieldUM.Error -> Text( + text = state.error.resolveReference(), + color = TangemTheme.colors.text.warning, + style = TangemTheme.typography.caption2, + textAlign = TextAlign.Center, + ) + is OnrampAmountSecondaryFieldUM.Loading -> TextShimmer( + style = TangemTheme.typography.caption2, + modifier = Modifier.width(TangemTheme.dimens.size62), + ) + } + } +} + +@Composable +private fun OnrampCurrencyIcon(currencyUM: OnrampCurrencyUM, modifier: Modifier = Modifier) { + Row( + modifier = modifier.clickable(onClick = currencyUM.onClick), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + AsyncImage( + modifier = Modifier + .size(TangemTheme.dimens.size40) + .clip(CircleShape), + model = currencyUM.iconUrl, + contentDescription = null, + ) + Icon( + modifier = Modifier.size(TangemTheme.dimens.size16), + painter = painterResource(id = R.drawable.ic_chevron_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + } +} \ 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 index bf30582c07..14318c4511 100644 --- 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 @@ -2,7 +2,9 @@ package com.tangem.features.onramp.main.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.FabPosition import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable @@ -23,7 +25,7 @@ import com.tangem.features.onramp.main.entity.OnrampMainComponentUM @Composable internal fun OnrampMainComponentContent(state: OnrampMainComponentUM, modifier: Modifier = Modifier) { Scaffold( - modifier = modifier, + modifier = modifier.imePadding(), contentWindowInsets = WindowInsetsZero, containerColor = TangemTheme.colors.background.secondary, topBar = { @@ -42,7 +44,7 @@ internal fun OnrampMainComponentContent(state: OnrampMainComponentUM, modifier: .wrapContentHeight() when (state) { is OnrampMainComponentUM.InitialLoading -> InitialLoading(modifier = contentModifier) - is OnrampMainComponentUM.Content -> Content(modifier = contentModifier) + is OnrampMainComponentUM.Content -> Content(modifier = contentModifier, state = state) } }, floatingActionButton = { @@ -85,8 +87,15 @@ private fun InitialLoading(modifier: Modifier = Modifier) { } @Composable -private fun Content(modifier: Modifier = Modifier) { - Box(modifier = modifier) +private fun Content(state: OnrampMainComponentUM.Content, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .verticalScroll(rememberScrollState()) + .navigationBarsPadding() + .padding(bottom = TangemTheme.dimens.spacing76), + ) { + OnrampAmountContent(state = state.amountBlockState) + } } @Composable 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 16d046651d..36fd583a12 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 @@ -63,7 +63,7 @@ internal class DefaultOnrampComponent @AssistedInject constructor( OnrampChild.Main -> onrampMainComponentFactory.create( context = childByContext(componentContext), params = OnrampMainComponent.Params( - currency = params.currency, + cryptoCurrency = params.cryptoCurrency, openSettings = { navigation.push(OnrampChild.Settings) }, ), ) From 4e28463b8a352d53d7144f6dd25f2de860671db5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 17 Nov 2024 16:31:12 +0300 Subject: [PATCH 36/99] 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 2e3c74646e..a988ee202a 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -89,7 +89,7 @@ markdownComposeView = "0.5.4" # region Tangem tangemBlockchainSdk = "develop-863" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-400" +tangemCardSdk = "develop-403" #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 546bcc908f9d0b2a5c1981b447632bdc6e854aae Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Nov 2024 10:44:43 +0200 Subject: [PATCH 37/99] Updated on 2026-08-14 --- .../api/common/adapter/DateTimeAdapter.kt | 9 +- .../api/common/adapter/LocalDateAdapter.kt | 8 +- .../response/TokenMarketInfoResponse.kt | 25 +++ core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-es/strings.xml | 18 +- core/res/src/main/res/values-fr/strings.xml | 1 + core/res/src/main/res/values-ja/strings.xml | 6 + core/res/src/main/res/values-ru/strings.xml | 17 +- .../src/main/res/values-uk-rUA/strings.xml | 15 +- core/res/src/main/res/values/strings.xml | 14 +- .../com/tangem/core/ui/res/TangemDimens.kt | 1 + .../converters/TokenMarketInfoConverter.kt | 54 +++++ .../tangem/domain/markets/TokenMarketInfo.kt | 21 ++ .../impl/model/MarketsTokenDetailsModel.kt | 3 + .../converters/SecurityScoreConverter.kt | 66 ++++-- .../converters/TokenMarketInfoConverter.kt | 15 +- .../formatter/MarketsDateTimeFormatters.kt | 14 +- .../impl/ui/MarketsTokenDetailsContent.kt | 3 + .../impl/ui/components/ScoreStarsBlock.kt | 96 +++++++++ .../impl/ui/components/SecurityScoreBlock.kt | 88 +------- .../ui/components/SecurityScoreBottomSheet.kt | 191 ++++++++++++++++++ .../ui/preview/SecurityScorePreviewData.kt | 60 ++++++ .../state/SecurityScoreBottomSheetContent.kt | 25 +++ .../details/impl/ui/state/SecurityScoreUM.kt | 3 +- 24 files changed, 624 insertions(+), 131 deletions(-) create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ScoreStarsBlock.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/SecurityScorePreviewData.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreBottomSheetContent.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/DateTimeAdapter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/DateTimeAdapter.kt index 5e637ed8c8..4149bf22f8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/DateTimeAdapter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/DateTimeAdapter.kt @@ -7,9 +7,12 @@ internal class DateTimeAdapter : JsonAdapter() { @FromJson override fun fromJson(reader: JsonReader): DateTime? { - val dateString = reader.nextString() ?: return null - - return DateTime.parse(dateString) + return if (reader.peek() == JsonReader.Token.NULL) { + reader.nextNull() + } else { + val dateString = reader.nextString() + DateTime.parse(dateString) + } } @ToJson diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/LocalDateAdapter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/LocalDateAdapter.kt index 6a7cd420c6..d87504268b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/LocalDateAdapter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/LocalDateAdapter.kt @@ -10,8 +10,12 @@ internal class LocalDateAdapter : JsonAdapter() { @FromJson override fun fromJson(reader: JsonReader): LocalDate? { - val dateString = reader.nextString() - return LocalDate.parse(dateString, formatter) + return if (reader.peek() == JsonReader.Token.NULL) { + reader.nextNull() + } else { + val dateString = reader.nextString() + return LocalDate.parse(dateString, formatter) + } } @ToJson diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt index 64b168072f..e18e222fd9 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt @@ -2,6 +2,7 @@ package com.tangem.datasource.api.markets.models.response import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.joda.time.DateTime import java.math.BigDecimal @JsonClass(generateAdapter = true) @@ -26,6 +27,8 @@ data class TokenMarketInfoResponse( val insights: Insights?, @Json(name = "metrics") val metrics: Metrics?, + @Json(name = "security_data") + val securityData: SecurityData?, @Json(name = "links") val links: Links?, @Json(name = "price_performance") @@ -144,6 +147,28 @@ data class TokenMarketInfoResponse( val allTime: Range?, ) + @JsonClass(generateAdapter = true) + data class SecurityData( + @Json(name = "total_security_score") + val totalSecurityScore: Float, + @Json(name = "provider_data") + val providerData: List, + ) + + @JsonClass(generateAdapter = true) + data class ProviderData( + @Json(name = "provider_id") + val providerId: String, + @Json(name = "provider_name") + val providerName: String, + @Json(name = "link") + val link: String?, + @Json(name = "security_score") + val securityScore: Float, + @Json(name = "last_audit_date") + val lastAuditDate: DateTime?, + ) + @JsonClass(generateAdapter = true) data class Range( @Json(name = "low_price") diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 5e53fcf4cc..65a4990969 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -568,7 +568,7 @@ Keine verfügbaren Anbieter für diese Währung Bezahlen mit Du kannst Deine Transaktion beim Drittanbieter %s abschließen. - Umleitung auf %s... + Umleitung auf %s … Unsere Dienstleistungen sind in diesem Land nicht verfügbar Änder oder bestätige bitte Dein Wohnsitz wurde identifiziert als diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index f8405d31f3..ee04d97a2f 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -180,6 +180,7 @@ Condiciones de uso Hoy Transacción fallida + Estado de la transacción Transacciones Transferencia Entiendo @@ -187,6 +188,7 @@ Inaccesible Termine el staking semana + con Dirección del contract copiada Redes disponibles @@ -427,6 +429,7 @@ El valor teórico total de una criptomoneda si todas las monedas que podrían existir estuvieran en circulación, incluidas las que actualmente no circulan. Valoración totalmente diluida Dato de Genesis + Vacío Alto Titulares El cambio en el número de poseedores de tokens dentro de un período de tiempo específico @@ -437,6 +440,7 @@ El cambio en la cantidad de liquidez disponible para el token durante el período de tiempo especificado Liquidez Índice de liquidez + Vacío Listado en Bajo Capital. de mercado @@ -453,6 +457,7 @@ Rendimiento de precios Repositorio Puntuación de seguridad + La puntuación de seguridad de un token es una métrica que evalúa el nivel de seguridad de una blockchain o token en función de varios factores y se genera a partir de las fuentes que se enumeran a continuación. Social Suministro total La cantidad máxima de monedas o tokens que pueden existir para una criptomoneda en particular @@ -509,12 +514,12 @@ Creando un backup Leer más sobre seed phrase - + Vacío Escriba estas %d palabras en el orden que se indica a continuación y guárdelas en un lugar seguro y secreto. Su seed phrase - + Vacío %d palabras Para importar su billetera, ingrese su seed phrase en el campo de abajo @@ -566,12 +571,15 @@ La cantidad a comprar debe ser como mínimo %s No hay proveedores disponibles para esta moneda Pagar con + Podrá completar su transacción en el proveedor externo, %s + Redirigiendo a %s... Nuestros servicios no están disponibles en este país Cámbielo o confírmelo Su residencia ha sido identificada como Residencia Seleccione el país correcto para garantizar opciones de pago y servicios precisos. Ajustes + Puede comprobar el estado de la transacción desde la página del token Vía Agrupar Por saldo @@ -804,7 +812,7 @@ Desunión Desbloquear Desbloqueando - La cantidad para el staking debe ser al menos %s + La cantidad para el unstaking debe ser al menos %s El monto excede el saldo apostado Sin staking Unstaking @@ -884,6 +892,7 @@ desde: %s a: %s validador: %s + Mínimo %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 @@ -949,6 +958,9 @@ %s red La dirección se copió correctamente Sin conexión a internet + Consíguelo ahora con un 10 %% de descuento + Accede a más de 13 000 criptomonedas. Compra, vende, intercambia y realiza staking con un solo toque.\nVincula hasta tres tarjetas para hacer copias de seguridad. + Descubre Tangem Wallet Ajusta de la wallet Tangem Use %s o escanee una tarjeta/anillo para desbloquear el acceso a su billetera diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index dba4e36c8b..c9983efdb7 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -884,6 +884,7 @@ de : %s à : %s validateur : %s + Minimum %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 diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index af02575293..3d8d8d15d5 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -177,6 +177,7 @@ 利用規約 今日 取引が失敗しました + 取引状況 取引 送金 わかりました @@ -184,6 +185,7 @@ アクセスできません ステーキング解除 + はい コントラクトアドレスをコピーしました! 利用可能なネットワーク @@ -567,6 +569,7 @@ 住居 正確なお支払い方法とサービスを確保するため、正しい国を選択してください。 設定 + トークンページから取引状況を確認できます 経由 グループ 残高順 @@ -943,6 +946,9 @@ %sネットワーク アドレスがクリップボードにコピーされました インターネット接続がありません + 今すぐ10 %オフで購入 + 1.3万種類以上の暗号資産にアクセス。ワンタップで買付、売却、スワップ、ステーキングが可能です。\nバックアップ用に最大3枚のカードを連携できます。 + Tangemウォレットを見る ウォレット設定 Tangem %sを使用するか、カード / リングをスキャンしてウォレットにアクセスしてください diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 952af02796..9a43af9607 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -183,6 +183,7 @@ Условиями использования Сегодня Ошибка транзакции + Статус транзакции Транзакции Перевод Я понял @@ -190,6 +191,7 @@ Недоступно Завершить стейкинг неделю + с Да Адрес контракта скопирован! Доступные сети @@ -576,7 +578,10 @@ Поиск по валюте Сумма покупки не может быть больше, чем %s Сумма покупки должна составлять минимум %s + Для данной валюты нет доступных провайдеров Оплата с + Вы сможете завершить транзакцию через сервис стороннего провайдера, %s + Переход к %s ... Наши сервисы недоступны в данной стране Настройки Через @@ -860,14 +865,14 @@ Балансы показаны Отменить Выбранная операция в данный момент недоступна. Попробуйте позже. - В данный момент покупка монеты %s недоступна. Следите за нашими обновлениями. + Покупка %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями. У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. - В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями. + Обмен %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями. Продажа средств станет доступной после завершения транзакции(-ий) в сети %s Отправка средств станет доступной после завершения транзакции(-ий) в сети %s - В данный момент продажа %s недоступна. Следите за нашими обновлениями. - В данный момент стейкинг монеты %s недоступен. Следите за нашими обновлениями. + Продажа %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями. + Стейкинг %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями. Сгенерировать XPUB Скрыть Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. @@ -893,6 +898,7 @@ от: %s на: %s валидатор: %s + Минимум %s Минимальная сумма транзакции равна %1$s. Попробовать снова Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d @@ -958,6 +964,9 @@ Сеть %s Адрес скопирован в буфер обмена Нет соединения с интернетом + Получить с 10%% скидкой + Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменяйте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. + Откройте Tangem Wallet Настройки кошелька Tangem Используйте %s или отсканируйте карту/кольцо, чтобы разблокировать доступ к вашему кошельку 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 c3f58becbb..8f07d981bf 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -186,6 +186,7 @@ Умовами використання Сьогодні Помилка транзакції + Статус транзакції Транзакції Переказ Я зрозумів @@ -193,6 +194,7 @@ Недоступно Скасувати стейкінг тиждень + з Так Адреса контракту скопійована! Доступні мережі @@ -465,6 +467,7 @@ Цінова ефективність Репозиторій Оцінка безпеки + Оцінка безпеки токена – це метрика, яка оцінює рівень безпеки блокчейну або токена на основі різних факторів і складається з джерел, перелічених нижче. Соцмережі Загальна проп. Максимальна кількість монет або токенів, яка може коли-небудь існувати для певної криптовалюти @@ -521,7 +524,7 @@ Резервна копія Дізнатися більше про seed-фразу - Запишіть це %d слово у порядку, вказаному нижче, та збережіть його у надійному місці. + Запишіть ці %d слова у порядку, вказаному нижче, та збережіть їх у надійному місці. Запишіть ці %d слів у порядку, вказаному нижче, та збережіть їх у надійному місці. Запишіть ці %d слів у порядку, вказаному нижче, та збережіть їх у надійному місці. @@ -578,12 +581,18 @@ Пошук за країною Недоступно Пошук по валюті + Сума покупки не може бути більше ніж %s + Сума покупки повинна бути не менше %s + Для данної валюти немає доступних провайдерів Оплата з + Ви зможете завершити транзакцію у стороннього постачальника, %s + Перенаправлення на %s... Наші сервіси недоступні в цій країні Змінити або підтвердити Ваше місце проживання визначено як Будь ласка, виберіть правильну країну, щоб забезпечити точні способи оплати та послуги. Параметри + Ви можете перевірити статус транзакції на сторінці токена Через Групами За балансом @@ -820,6 +829,7 @@ Розблокування Розблокувати Розблокування + Сума для виведення зі стейкінгу повинна бути не менше %s Сума перевищує баланс стейкінгу Вивід зі стейкінгу Зняти зі стейкінгу @@ -849,7 +859,7 @@ У суму входить:\n- комісія провайдера\n- мережева комісія за відправку %1$s з біржі назад на адресу користувача. \n\nПроскакування провайдера становить до %2$s Сума включає комісію постачальника послуг. Сума включає комісію провайдера послуг. \n\nПроскакування провайдера до %s - Комісії + Інформація Всі децентралізовані біржі вимагають схвалення, щоб запобігти доступу смарт-контрактів до вашого гаманця без вашого дозволу. За задумом смарт-контракти не можуть отримати доступ до ваших токенів без вашого схвалення. \"Розблоковуючи\" свої токени, ви дозволяєте смарт-контракту 1inch витрачати ваші активи. Майнери мережі отримують плату за газ (сплачену вами), щоб зафіксувати цю дію в блокчейні. Ви можете обміняти свій токен після того, як дасте дозвіл. Підтвердити Помилка при розрахунку комісії. Будь ласка, надішліть відгук до служби підтримки. @@ -899,6 +909,7 @@ від: %s до: %s валідатор: %s + Мінімум %s Мінімальна сума транзакції становить %1$s. Спробуйте знову Ви відсканували одну й ту саму картку. Для створення twin-гаманця вам потрібно відсканувати картку з номером %d diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 2592e85ed6..6978552ae0 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -180,6 +180,7 @@ Terms of Use Today Transaction failed + Transaction status Transactions Transfer I understand @@ -187,6 +188,7 @@ Unreachable Unstake week + with Yes Contract address copied! Available networks @@ -575,6 +577,7 @@ Residence Please select the correct country to ensure accurate payment options and services. Settings + You can check the transaction status from the token page Via Group By balance @@ -853,14 +856,14 @@ Balances shown Undo This operation is currently unavailable. Please try again later. - Buying %s is not available at the moment. Please check our updates. + Buying %s is not supported by current providers, but we are working to add more options. You do not have funds to sell. Top up your account to be able to sell funds from it. You do not have funds to send. Top up your account to be able to send funds from it. - Swapping %s is not available at the moment. Please check our updates. + Swapping %s is not supported by current providers, but we are working to add more options. Selling funds will be available once the pending transaction(s) in network %s is complete Sending funds will be available once the pending transaction(s) in network %s is complete - Selling %s is not available at the moment. Please check our updates. - Staking %s is not available at the moment. Please check our updates. + Selling %s is not supported by current providers, but we are working to add more options. + Staking %s is not supported by current providers, but we are working to add more options. Generate XPUB Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. @@ -953,6 +956,9 @@ %s network Address was copied to clipboard No internet connection + Get now with 10%% off + Access 13,000+ cryptocurrencies. Buy, sell, swap, and stake with a single tap.\nLink up to three cards for a backup. + Discover Tangem Wallet Wallet settings Tangem Use %s or scan a card/ring to unlock access to your wallet diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 9617ee7825..7698a8067d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -36,6 +36,7 @@ data class TangemDimens internal constructor( val radius26: Dp = 26.dp, val radius28: Dp = 28.dp, val radius36: Dp = 36.dp, + val radius40: Dp = 40.dp, // endregion Radius // region Size val size0: Dp = 0.dp, diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt index 7df1e1a203..7910c58025 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt @@ -1,9 +1,11 @@ package com.tangem.data.markets.converters +import android.net.Uri import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.isSupportedInApp import com.tangem.datasource.api.markets.models.response.TokenMarketInfoResponse +import com.tangem.domain.markets.BuildConfig import com.tangem.domain.markets.TokenMarketInfo import com.tangem.domain.markets.TokenQuotes import com.tangem.utils.converter.Converter @@ -11,6 +13,9 @@ import java.math.BigDecimal internal object TokenMarketInfoConverter : Converter { + private const val PROD_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/security_provider/" + private const val DEV_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api.dev/security_provider/" + override fun convert(value: TokenMarketInfoResponse): TokenMarketInfo { return with(value) { TokenMarketInfo( @@ -23,6 +28,7 @@ internal object TokenMarketInfoConverter : Converter= 2) { + parts.takeLast(2).joinToString(".") + } else { + host + } + }.getOrNull() + } + + private fun getImageHost(): String { + return if (BuildConfig.TESTER_MENU_ENABLED) { + DEV_IMAGE_HOST + } else { + PROD_IMAGE_HOST + } + } } \ No newline at end of file diff --git a/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt b/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt index f8f62337a0..c53274d7a3 100644 --- a/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt +++ b/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt @@ -1,5 +1,6 @@ package com.tangem.domain.markets +import org.joda.time.DateTime import java.math.BigDecimal data class TokenMarketInfo( @@ -12,6 +13,7 @@ data class TokenMarketInfo( val fullDescription: String?, val insights: Insights?, val metrics: Metrics?, + val securityData: SecurityData?, val links: Links?, val pricePerformance: PricePerformance?, val exchangesAmount: Int?, @@ -51,6 +53,25 @@ data class TokenMarketInfo( val fullyDilutedValuation: BigDecimal?, ) + data class SecurityData( + val totalSecurityScore: Float, + val providerData: List, + ) + + data class ProviderData( + val providerId: String, + val providerName: String, + val urlData: ProviderUrlData?, + val iconUrl: String, + val securityScore: Float, + val lastAuditDate: DateTime?, + ) + + data class ProviderUrlData( + val fullUrl: String, + val rootHost: String?, + ) + data class Links( val officialLinks: List?, val social: List?, 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 9d7e300646..aa748cfe5f 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 @@ -88,6 +88,9 @@ internal class MarketsTokenDetailsModel @Inject constructor( // === Analytics === analyticsEventHandler.send(analyticsEventBuilder.linkClicked(linkTitle = link.title)) }, + onSecurityScoreProviderLinkClick = { + urlOpener.openUrl(it) + }, // === Analytics === onPricePerformanceIntervalChanged = { analyticsEventHandler.send( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt index 302aeb7258..906842ebf0 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt @@ -1,35 +1,57 @@ package com.tangem.features.markets.details.impl.model.converters import androidx.compose.runtime.Stable +import com.tangem.core.ui.extensions.pluralReference import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.features.markets.details.impl.model.formatter.MarketsDateTimeFormatters +import com.tangem.features.markets.details.impl.ui.state.SecurityScoreBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.SecurityScoreUM import com.tangem.features.markets.impl.R import com.tangem.utils.converter.Converter -// TODO implement when backend is ready @Stable internal class SecurityScoreConverter( - private val onInfoClick: (InfoBottomSheetContent) -> Unit, -) : Converter { + private val onInfoClick: (SecurityScoreBottomSheetContent) -> Unit, + private val onSecurityScoreProviderLinkClick: (String) -> Unit, +) : Converter { - override fun convert(value: Unit): SecurityScoreUM { - return with(value) { - SecurityScoreUM( - score = 4.7f, - description = "Based on 3 ratings", - onInfoClick = { - onInfoClick( - InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_security_score), - body = stringReference("markets_token_details_security_score_description"), - // FIXME - // resourceReference(R.string.markets_token_details_security_score_description) - ), - ) - }, - ) - } + override fun convert(value: TokenMarketInfo.SecurityData): SecurityScoreUM { + val ratingsCount = value.providerData.size + return SecurityScoreUM( + score = value.totalSecurityScore, + description = pluralReference( + id = R.plurals.markets_token_details_based_on_ratings, + count = ratingsCount, + formatArgs = wrappedList(ratingsCount), + ), + onInfoClick = { + onInfoClick( + SecurityScoreBottomSheetContent( + title = resourceReference(R.string.markets_token_details_security_score), + description = resourceReference(R.string.markets_token_details_security_score_description), + providers = value.providerData.map { + SecurityScoreBottomSheetContent.SecurityScoreProviderUM( + name = it.providerName, + lastAuditDate = it.lastAuditDate?.let { date -> + MarketsDateTimeFormatters.formatAsDate(date.millis) + }, + score = it.securityScore, + urlData = it.urlData?.let { + urlData -> + SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData( + fullUrl = urlData.fullUrl, + rootHost = urlData.rootHost, + ) + }, + iconUrl = it.iconUrl, + ) + }, + onProviderLinkClick = onSecurityScoreProviderLinkClick, + ), + ) + }, + ) } } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt index 3ab8df1094..d857983a3a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt @@ -1,10 +1,10 @@ package com.tangem.features.markets.details.impl.model.converters import androidx.compose.runtime.Stable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.PriceChangeInterval import com.tangem.domain.markets.TokenMarketInfo -import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.LinksUM import com.tangem.features.markets.details.impl.ui.state.ListedOnUM import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM @@ -12,11 +12,13 @@ import com.tangem.utils.Provider import com.tangem.utils.converter.Converter @Stable +@Suppress("LongParameterList") internal class TokenMarketInfoConverter( private val appCurrency: Provider, - private val onInfoClick: (InfoBottomSheetContent) -> Unit, + private val onInfoClick: (TangemBottomSheetConfigContent) -> Unit, private val onListedOnClick: (Int) -> Unit, onLinkClick: (LinksUM.Link) -> Unit, + onSecurityScoreProviderLinkClick: (String) -> Unit, onPricePerformanceIntervalChanged: (PriceChangeInterval) -> Unit, onInsightsIntervalChanged: (PriceChangeInterval) -> Unit, ) : Converter { @@ -27,9 +29,10 @@ internal class TokenMarketInfoConverter( onIntervalChanged = onInsightsIntervalChanged, ) - @Suppress("UnusedPrivateMember") - // TODO second markets iteration - private val securityScoreConverter = SecurityScoreConverter(onInfoClick = onInfoClick) + private val securityScoreConverter = SecurityScoreConverter( + onInfoClick = onInfoClick, + onSecurityScoreProviderLinkClick = onSecurityScoreProviderLinkClick, + ) private val pricePerformanceConverter = PricePerformanceConverter( appCurrency = appCurrency, onIntervalChanged = onPricePerformanceIntervalChanged, @@ -46,7 +49,7 @@ internal class TokenMarketInfoConverter( val exchangesAmount = value.exchangesAmount return MarketsTokenDetailsUM.InformationBlocks( insights = value.insights?.let { insightsConverter.convert(it) }, - securityScore = null, + securityScore = value.securityData?.let { securityScoreConverter.convert(it) }, metrics = value.metrics?.let { metricsConverter.convert(it) }, pricePerformance = value.pricePerformance?.let { pricePerformanceConverter.convert( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/formatter/MarketsDateTimeFormatters.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/formatter/MarketsDateTimeFormatters.kt index 5abc296917..46ba611519 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/formatter/MarketsDateTimeFormatters.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/formatter/MarketsDateTimeFormatters.kt @@ -55,7 +55,7 @@ internal object MarketsDateTimeFormatters { R.string.common_range_with_space, wrappedList( stringReference( - startTimestamp.formatAsDateTime(MarketsDateTimeFormatters.dateTimeMMMFormatter), + startTimestamp.formatAsDateTime(dateTimeMMMFormatter), ), resourceReference(R.string.common_now), ), @@ -68,7 +68,7 @@ internal object MarketsDateTimeFormatters { R.string.common_range_with_space, wrappedList( stringReference( - startTimestamp.formatAsDateTime(MarketsDateTimeFormatters.dateFormatter), + startTimestamp.formatAsDateTime(dateFormatter), ), resourceReference(R.string.common_now), ), @@ -89,7 +89,7 @@ internal object MarketsDateTimeFormatters { R.string.common_range_with_space, wrappedList( stringReference( - markerTimestamp.toLong().formatAsDateTime(MarketsDateTimeFormatters.dateTimeMMMFormatter), + markerTimestamp.toLong().formatAsDateTime(dateTimeMMMFormatter), ), resourceReference(R.string.common_now), ), @@ -103,7 +103,7 @@ internal object MarketsDateTimeFormatters { R.string.common_range_with_space, wrappedList( stringReference( - markerTimestamp.toLong().formatAsDateTime(MarketsDateTimeFormatters.dateFormatter), + markerTimestamp.toLong().formatAsDateTime(dateFormatter), ), resourceReference(R.string.common_now), ), @@ -128,10 +128,14 @@ internal object MarketsDateTimeFormatters { fun getDefaultDateTimeString(interval: PriceChangeInterval, currentTimestamp: Long): TextReference { return formatDateByInterval( interval = interval, - startTimestamp = MarketsDateTimeFormatters.getStartTimestampByInterval( + startTimestamp = getStartTimestampByInterval( interval = interval, currentTimestamp = currentTimestamp, ), ) } + + fun formatAsDate(timestamp: Long): String { + return timestamp.formatAsDateTime(dateFormatter) + } } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt index 4e46c48655..59c5d3d8f1 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt @@ -37,6 +37,7 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.markets.PriceChangeInterval +import com.tangem.features.markets.details.impl.ui.components.* import com.tangem.features.markets.details.impl.ui.components.ExchangesBottomSheet import com.tangem.features.markets.details.impl.ui.components.InfoBottomSheet import com.tangem.features.markets.details.impl.ui.components.MarketTokenDetailsChart @@ -44,6 +45,7 @@ import com.tangem.features.markets.details.impl.ui.components.tokenMarketDetails import com.tangem.features.markets.details.impl.ui.state.ExchangesBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM +import com.tangem.features.markets.details.impl.ui.state.SecurityScoreBottomSheetContent import com.tangem.features.markets.impl.R import kotlinx.collections.immutable.persistentListOf @@ -72,6 +74,7 @@ internal fun MarketsTokenDetailsContent( when (state.bottomSheetConfig.content) { is InfoBottomSheetContent -> InfoBottomSheet(config = state.bottomSheetConfig) + is SecurityScoreBottomSheetContent -> SecurityScoreBottomSheet(config = state.bottomSheetConfig) is ExchangesBottomSheetContent -> ExchangesBottomSheet(config = state.bottomSheetConfig) } } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ScoreStarsBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ScoreStarsBlock.kt new file mode 100644 index 0000000000..d7d047c38b --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ScoreStarsBlock.kt @@ -0,0 +1,96 @@ +package com.tangem.features.markets.details.impl.ui.components + +import androidx.annotation.FloatRange +import androidx.compose.foundation.layout.* +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.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.markets.impl.R +import kotlin.math.round + +private const val STARS_COUNT = 5 + +@Composable +internal fun ScoreStarsBlock( + score: Float, + horizontalSpacing: Dp, + scoreTextStyle: TextStyle, + modifier: Modifier = Modifier, +) { + val rounded = score.roundTo1decimal() + val percentage = rounded / STARS_COUNT + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(horizontalSpacing), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = rounded.toString(), + style = scoreTextStyle, + color = TangemTheme.colors.text.primary1, + ) + Stars(fraction = percentage) + } +} + +@Suppress("MagicNumber") +@Composable +private fun Stars(@FloatRange(0.0, 1.0) fraction: Float = 0f) { + val grayColor = TangemTheme.colors.icon.inactive + + Row( + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), + verticalAlignment = Alignment.CenterVertically, + ) { + repeat(times = 5) { i -> + Box( + modifier = Modifier.size(TangemTheme.dimens.size16), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier + .requiredSize(16.dp) + .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) + .drawWithCache { + onDrawWithContent { + val starFraction = ((fraction - i * 0.2) / 0.2).coerceIn(0.0, 1.0) + val starFractionFloat = starFraction + .toFloat() + .roundTo1decimal() + + drawContent() + drawRect( + color = grayColor, + topLeft = Offset(x = size.width * starFractionFloat, y = 0f), + size = Size(size.width * (1 - starFractionFloat), size.height), + blendMode = BlendMode.SrcIn, + ) + } + }, + imageVector = ImageVector.vectorResource(R.drawable.ic_star_24), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + ) + } + } + } +} + +@Suppress("MagicNumber") +private fun Float.roundTo1decimal(): Float { + return round(this * 10) / 10 +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt index 66a75660ef..db8023992c 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt @@ -1,42 +1,28 @@ package com.tangem.features.markets.details.impl.ui.components import android.content.res.Configuration -import androidx.annotation.FloatRange import androidx.compose.foundation.layout.* -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.drawWithCache -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.BlendMode -import androidx.compose.ui.graphics.CompositingStrategy -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.block.information.InformationBlock import com.tangem.core.ui.components.text.TooltipText +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.core.ui.utils.PreviewShimmerContainer import com.tangem.features.markets.details.impl.ui.state.SecurityScoreUM import com.tangem.features.markets.impl.R -import kotlin.math.round - -private const val STARS_COUNT = 5 @Composable internal fun SecurityScoreBlock(state: SecurityScoreUM, modifier: Modifier = Modifier) { - val rounded = state.score.roundTo1decimal() - val percentage = rounded / STARS_COUNT InformationBlock( modifier = modifier, title = { @@ -51,7 +37,7 @@ internal fun SecurityScoreBlock(state: SecurityScoreUM, modifier: Modifier = Mod ) Text( - text = state.description, + text = state.description.resolveReference(), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, maxLines = 1, @@ -60,72 +46,18 @@ internal fun SecurityScoreBlock(state: SecurityScoreUM, modifier: Modifier = Mod } }, action = { - Row( + ScoreStarsBlock( modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing6), - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = rounded.toString(), - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.primary1, - ) - Stars(fraction = percentage) - } + score = state.score, + scoreTextStyle = TangemTheme.typography.body1, + horizontalSpacing = TangemTheme.dimens.spacing8, + ) }, ) } -@Suppress("MagicNumber") @Composable -private fun Stars(@FloatRange(0.0, 1.0) fraction: Float = 0f) { - val grayColor = TangemTheme.colors.icon.inactive - - Row( - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), - verticalAlignment = Alignment.CenterVertically, - ) { - repeat(times = 5) { i -> - Box( - modifier = Modifier.size(TangemTheme.dimens.size16), - contentAlignment = Alignment.Center, - ) { - Icon( - modifier = Modifier - .requiredSize(13.dp) - .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) - .drawWithCache { - onDrawWithContent { - val starFraction = ((fraction - i * 0.2) / 0.2).coerceIn(0.0, 1.0) - val starFractionFloat = starFraction - .toFloat() - .roundTo1decimal() - - drawContent() - drawRect( - color = grayColor, - topLeft = Offset(x = size.width * starFractionFloat, y = 0f), - size = Size(size.width * (1 - starFractionFloat), size.height), - blendMode = BlendMode.SrcIn, - ) - } - }, - imageVector = ImageVector.vectorResource(R.drawable.ic_star_24), - contentDescription = null, - tint = TangemTheme.colors.icon.accent, - ) - } - } - } -} - -@Suppress("MagicNumber") -private fun Float.roundTo1decimal(): Float { - return round(this * 10) / 10 -} - -@Composable -internal fun SecurityScorePlaceHolder(modifier: Modifier = Modifier) { +private fun SecurityScorePlaceHolder(modifier: Modifier = Modifier) { InformationBlock( modifier = modifier, title = { @@ -177,7 +109,7 @@ private fun ContentPreview() { SecurityScoreBlock( state = SecurityScoreUM( score = 3.5f, - description = "Based on 3 ratings", + description = stringReference("Based on 3 ratings"), onInfoClick = {}, ), ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt new file mode 100644 index 0000000000..69cf38aa34 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt @@ -0,0 +1,191 @@ +package com.tangem.features.markets.details.impl.ui.components + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.util.fastForEachIndexed +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetTitle +import com.tangem.core.ui.components.inputrow.inner.DividerContainer +import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.markets.details.impl.ui.preview.SecurityScorePreviewData +import com.tangem.features.markets.impl.R +import com.tangem.features.markets.details.impl.ui.state.SecurityScoreBottomSheetContent + +@Composable +internal fun SecurityScoreBottomSheet(config: TangemBottomSheetConfig) { + val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } + + TangemBottomSheet( + config = config, + containerColor = TangemTheme.colors.background.secondary, + addBottomInsets = false, + title = { TangemBottomSheetTitle(title = it.title) }, + content = { content -> + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(horizontal = TangemTheme.dimens.spacing16), + ) { + Text( + text = content.description.resolveReference(), + style = TangemTheme.typography.body2.copy( + color = TangemTheme.colors.text.secondary, + ), + ) + + SpacerH12() + content.providers.fastForEachIndexed { index, provider -> + DividerContainer( + modifier = Modifier + .roundedShapeItemDecoration( + currentIndex = index, + lastIndex = content.providers.lastIndex, + addDefaultPadding = false, + ) + .background(TangemTheme.colors.background.action), + showDivider = index != content.providers.lastIndex, + ) { + SecurityScoreProviderRow( + providerUM = provider, + onLinkClick = content.onProviderLinkClick, + ) + } + } + + SpacerH16() + SpacerH(bottomBarHeight) + } + }, + ) +} + +@Composable +private fun SecurityScoreProviderRow( + providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM, + onLinkClick: (String) -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing12) + .heightIn(min = TangemTheme.dimens.size68), + verticalAlignment = Alignment.CenterVertically, + ) { + SubcomposeAsyncImage( + modifier = Modifier + .size(size = TangemTheme.dimens.size40) + .clip(TangemTheme.shapes.roundedCorners8), + model = ImageRequest.Builder(context = LocalContext.current) + .data(providerUM.iconUrl) + .crossfade(enable = true) + .allowHardware(false) + .build(), + loading = { RectangleShimmer(radius = TangemTheme.dimens.radius8) }, + error = { RectangleShimmer(radius = TangemTheme.dimens.radius8) }, + contentDescription = null, + ) + + Column( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing12), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), + ) { + Text( + text = providerUM.name, + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + ) + providerUM.lastAuditDate?.let { + Text( + text = providerUM.lastAuditDate, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ) + } + } + + SpacerWMax() + + Column( + horizontalAlignment = Alignment.End, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), + modifier = Modifier.clickable( + enabled = providerUM.urlData != null, + indication = ripple(bounded = false), + interactionSource = remember { MutableInteractionSource() }, + onClick = { providerUM.urlData?.let { onLinkClick(it.fullUrl) } }, + ), + ) { + ScoreStarsBlock( + score = providerUM.score, + scoreTextStyle = TangemTheme.typography.body2, + horizontalSpacing = TangemTheme.dimens.spacing3, + ) + + UrlBlock(providerUM) + } + } +} + +@Composable +private fun UrlBlock(providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM) { + val urlData = providerUM.urlData + val rootHost = urlData?.rootHost + if (urlData != null && rootHost != null) { + Row( + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), + ) { + Text( + text = urlData.rootHost, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ) + Icon( + modifier = Modifier + .size(TangemTheme.dimens.size16), + painter = painterResource(id = R.drawable.ic_arrow_top_right_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) + } + } +} + +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun SecurityScoreBottomSheetPreview() { + TangemThemePreview { + SecurityScoreBottomSheet( + config = TangemBottomSheetConfig( + isShow = true, + onDismissRequest = {}, + content = SecurityScorePreviewData.bottomSheetContent, + ), + ) + } +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/SecurityScorePreviewData.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/SecurityScorePreviewData.kt new file mode 100644 index 0000000000..45e80ad9b7 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/SecurityScorePreviewData.kt @@ -0,0 +1,60 @@ +package com.tangem.features.markets.details.impl.ui.preview + +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.markets.details.impl.ui.state.SecurityScoreBottomSheetContent + +internal object SecurityScorePreviewData { + + val bottomSheetContent = SecurityScoreBottomSheetContent( + title = stringReference("Security score"), + description = stringReference( + "Security score of a token is a metric that assesses the " + + "security level of a blockchain or token based on various factors and is compiled from " + + "the sources listed below.", + ), + providers = listOf( + SecurityScoreBottomSheetContent.SecurityScoreProviderUM( + name = "Moralis", + lastAuditDate = "21.10.2024", + score = 4.9F, + urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData( + fullUrl = "https://moralis.com/", + rootHost = "moralis.com", + ), + iconUrl = "", + ), + SecurityScoreBottomSheetContent.SecurityScoreProviderUM( + name = "Certik", + lastAuditDate = "10.07.2024", + score = 4.6F, + urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData( + fullUrl = "https://certik.com/", + rootHost = "certik.com", + ), + iconUrl = "", + ), + SecurityScoreBottomSheetContent.SecurityScoreProviderUM( + name = "Cyberscope", + lastAuditDate = "25.06.2023", + score = 4.5F, + urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData( + fullUrl = "https://cyberscope.com/", + rootHost = "cyberscope.com", + ), + iconUrl = "", + ), + SecurityScoreBottomSheetContent.SecurityScoreProviderUM( + name = "TokenInsight", + lastAuditDate = "17.01.2022", + score = 4.0F, + urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData( + fullUrl = "https://tokeninsight.com/", + rootHost = "tokeninsight.com", + ), + iconUrl = "", + ), + + ), + onProviderLinkClick = {}, + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreBottomSheetContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreBottomSheetContent.kt new file mode 100644 index 0000000000..bfac7fcab1 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreBottomSheetContent.kt @@ -0,0 +1,25 @@ +package com.tangem.features.markets.details.impl.ui.state + +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.extensions.TextReference + +internal data class SecurityScoreBottomSheetContent( + val title: TextReference, + val description: TextReference, + val providers: List, + val onProviderLinkClick: (String) -> Unit, +) : TangemBottomSheetConfigContent { + + data class SecurityScoreProviderUM( + val name: String, + val lastAuditDate: String?, + val score: Float, + val urlData: SecurityScoreProviderUrlData?, + val iconUrl: String?, + ) + + data class SecurityScoreProviderUrlData( + val fullUrl: String, + val rootHost: String?, + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreUM.kt index 35f35d4b9c..d4ebd3a9ee 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreUM.kt @@ -1,9 +1,10 @@ package com.tangem.features.markets.details.impl.ui.state import androidx.annotation.FloatRange +import com.tangem.core.ui.extensions.TextReference internal data class SecurityScoreUM( @FloatRange(from = 0.0, to = 5.0) val score: Float, - val description: String, + val description: TextReference, val onInfoClick: () -> Unit, ) \ No newline at end of file From 6ac5724e56d9fee4c2142dd5796eae1ba6e332b3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Nov 2024 14:40:19 +0200 Subject: [PATCH 38/99] Updated on 2026-08-14 --- .../converters/TokenMarketInfoConverter.kt | 10 +++++----- .../tangem/domain/markets/TokenMarketInfo.kt | 18 +++++++++--------- .../analytics/MarketDetailsAnalyticsEvent.kt | 13 +++++++++++++ .../impl/model/MarketsTokenDetailsModel.kt | 13 ++++++++++++- .../model/converters/SecurityScoreConverter.kt | 12 ++++++------ .../converters/TokenMarketInfoConverter.kt | 6 ++++-- .../ui/components/SecurityScoreBottomSheet.kt | 4 ++-- .../ui/preview/SecurityScorePreviewData.kt | 8 ++++---- .../state/SecurityScoreBottomSheetContent.kt | 16 ++++++++-------- 9 files changed, 63 insertions(+), 37 deletions(-) diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt index 7910c58025..85afc94699 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt @@ -114,12 +114,12 @@ internal object TokenMarketInfoConverter : Converter, + val securityScoreProviderData: List, ) - data class ProviderData( + data class SecurityScoreProvider( val providerId: String, val providerName: String, - val urlData: ProviderUrlData?, + val urlData: UrlData?, val iconUrl: String, val securityScore: Float, val lastAuditDate: DateTime?, - ) - - data class ProviderUrlData( - val fullUrl: String, - val rootHost: String?, - ) + ) { + data class UrlData( + val fullUrl: String, + val rootHost: String?, + ) + } data class Links( val officialLinks: List?, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt index 3216b03dd3..85d934fd13 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt @@ -51,6 +51,19 @@ internal class MarketDetailsAnalyticsEvent( "Token" to token.symbol, ), ) + + fun securityScoreOpened() = MarketDetailsAnalyticsEvent( + event = "Security Score Info", + params = mapOf("Token" to token.symbol), + ) + + fun securityScoreProviderClicked(provider: String) = MarketDetailsAnalyticsEvent( + event = "Security Score Provider Clicked", + params = mapOf( + "Token" to token.symbol, + "Provider" to provider, + ), + ) } enum class IntervalType(val source: String) { 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 aa748cfe5f..b6d4827fda 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 @@ -88,8 +88,19 @@ internal class MarketsTokenDetailsModel @Inject constructor( // === Analytics === analyticsEventHandler.send(analyticsEventBuilder.linkClicked(linkTitle = link.title)) }, + onSecurityScoreInfoClick = { + showBottomSheet(it) + + // === Analytics === + analyticsEventHandler.send(analyticsEventBuilder.securityScoreOpened()) + }, onSecurityScoreProviderLinkClick = { - urlOpener.openUrl(it) + it.urlData?.fullUrl?.let { url -> + urlOpener.openUrl(url) + } + + // === Analytics === + analyticsEventHandler.send(analyticsEventBuilder.securityScoreProviderClicked(it.name)) }, // === Analytics === onPricePerformanceIntervalChanged = { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt index 906842ebf0..aee019594a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/SecurityScoreConverter.kt @@ -13,12 +13,12 @@ import com.tangem.utils.converter.Converter @Stable internal class SecurityScoreConverter( - private val onInfoClick: (SecurityScoreBottomSheetContent) -> Unit, - private val onSecurityScoreProviderLinkClick: (String) -> Unit, + private val onSecurityScoreInfoClick: (SecurityScoreBottomSheetContent) -> Unit, + private val onSecurityScoreProviderLinkClick: (SecurityScoreBottomSheetContent.SecurityScoreProviderUM) -> Unit, ) : Converter { override fun convert(value: TokenMarketInfo.SecurityData): SecurityScoreUM { - val ratingsCount = value.providerData.size + val ratingsCount = value.securityScoreProviderData.size return SecurityScoreUM( score = value.totalSecurityScore, description = pluralReference( @@ -27,11 +27,11 @@ internal class SecurityScoreConverter( formatArgs = wrappedList(ratingsCount), ), onInfoClick = { - onInfoClick( + onSecurityScoreInfoClick( SecurityScoreBottomSheetContent( title = resourceReference(R.string.markets_token_details_security_score), description = resourceReference(R.string.markets_token_details_security_score_description), - providers = value.providerData.map { + providers = value.securityScoreProviderData.map { SecurityScoreBottomSheetContent.SecurityScoreProviderUM( name = it.providerName, lastAuditDate = it.lastAuditDate?.let { date -> @@ -40,7 +40,7 @@ internal class SecurityScoreConverter( score = it.securityScore, urlData = it.urlData?.let { urlData -> - SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData( + SecurityScoreBottomSheetContent.SecurityScoreProviderUM.UrlData( fullUrl = urlData.fullUrl, rootHost = urlData.rootHost, ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt index d857983a3a..85c354906a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt @@ -8,6 +8,7 @@ import com.tangem.domain.markets.TokenMarketInfo import com.tangem.features.markets.details.impl.ui.state.LinksUM import com.tangem.features.markets.details.impl.ui.state.ListedOnUM import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM +import com.tangem.features.markets.details.impl.ui.state.SecurityScoreBottomSheetContent import com.tangem.utils.Provider import com.tangem.utils.converter.Converter @@ -17,8 +18,9 @@ internal class TokenMarketInfoConverter( private val appCurrency: Provider, private val onInfoClick: (TangemBottomSheetConfigContent) -> Unit, private val onListedOnClick: (Int) -> Unit, + onSecurityScoreInfoClick: (SecurityScoreBottomSheetContent) -> Unit, onLinkClick: (LinksUM.Link) -> Unit, - onSecurityScoreProviderLinkClick: (String) -> Unit, + onSecurityScoreProviderLinkClick: (SecurityScoreBottomSheetContent.SecurityScoreProviderUM) -> Unit, onPricePerformanceIntervalChanged: (PriceChangeInterval) -> Unit, onInsightsIntervalChanged: (PriceChangeInterval) -> Unit, ) : Converter { @@ -30,7 +32,7 @@ internal class TokenMarketInfoConverter( ) private val securityScoreConverter = SecurityScoreConverter( - onInfoClick = onInfoClick, + onSecurityScoreInfoClick = onSecurityScoreInfoClick, onSecurityScoreProviderLinkClick = onSecurityScoreProviderLinkClick, ) private val pricePerformanceConverter = PricePerformanceConverter( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt index 69cf38aa34..6841aa66c0 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt @@ -86,7 +86,7 @@ internal fun SecurityScoreBottomSheet(config: TangemBottomSheetConfig) { @Composable private fun SecurityScoreProviderRow( providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM, - onLinkClick: (String) -> Unit, + onLinkClick: (SecurityScoreBottomSheetContent.SecurityScoreProviderUM) -> Unit, ) { Row( modifier = Modifier @@ -137,7 +137,7 @@ private fun SecurityScoreProviderRow( enabled = providerUM.urlData != null, indication = ripple(bounded = false), interactionSource = remember { MutableInteractionSource() }, - onClick = { providerUM.urlData?.let { onLinkClick(it.fullUrl) } }, + onClick = { onLinkClick(providerUM) }, ), ) { ScoreStarsBlock( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/SecurityScorePreviewData.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/SecurityScorePreviewData.kt index 45e80ad9b7..d80e49af6c 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/SecurityScorePreviewData.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/SecurityScorePreviewData.kt @@ -17,7 +17,7 @@ internal object SecurityScorePreviewData { name = "Moralis", lastAuditDate = "21.10.2024", score = 4.9F, - urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData( + urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUM.UrlData( fullUrl = "https://moralis.com/", rootHost = "moralis.com", ), @@ -27,7 +27,7 @@ internal object SecurityScorePreviewData { name = "Certik", lastAuditDate = "10.07.2024", score = 4.6F, - urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData( + urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUM.UrlData( fullUrl = "https://certik.com/", rootHost = "certik.com", ), @@ -37,7 +37,7 @@ internal object SecurityScorePreviewData { name = "Cyberscope", lastAuditDate = "25.06.2023", score = 4.5F, - urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData( + urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUM.UrlData( fullUrl = "https://cyberscope.com/", rootHost = "cyberscope.com", ), @@ -47,7 +47,7 @@ internal object SecurityScorePreviewData { name = "TokenInsight", lastAuditDate = "17.01.2022", score = 4.0F, - urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUrlData( + urlData = SecurityScoreBottomSheetContent.SecurityScoreProviderUM.UrlData( fullUrl = "https://tokeninsight.com/", rootHost = "tokeninsight.com", ), diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreBottomSheetContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreBottomSheetContent.kt index bfac7fcab1..ceb100bdfb 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreBottomSheetContent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/SecurityScoreBottomSheetContent.kt @@ -7,19 +7,19 @@ internal data class SecurityScoreBottomSheetContent( val title: TextReference, val description: TextReference, val providers: List, - val onProviderLinkClick: (String) -> Unit, + val onProviderLinkClick: (SecurityScoreProviderUM) -> Unit, ) : TangemBottomSheetConfigContent { data class SecurityScoreProviderUM( val name: String, val lastAuditDate: String?, val score: Float, - val urlData: SecurityScoreProviderUrlData?, + val urlData: UrlData?, val iconUrl: String?, - ) - - data class SecurityScoreProviderUrlData( - val fullUrl: String, - val rootHost: String?, - ) + ) { + data class UrlData( + val fullUrl: String, + val rootHost: String?, + ) + } } \ No newline at end of file From a80542d5dac166966bcd2f349545fcaed77f36bd Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Nov 2024 16:30:50 +0300 Subject: [PATCH 39/99] Updated on 2026-08-14 --- .../DefaultOnboardingMultiWalletComponent.kt | 96 ++++++++++++++----- .../backup/MultiWalletBackupComponent.kt | 8 +- .../backup/model/MultiWalletBackupModel.kt | 67 ++++++------- .../impl/child/backup/ui/MultiWalletBackup.kt | 82 ++++------------ .../backup/ui/state/MultiWalletBackupUM.kt | 38 +++----- .../Wallet1ChooseOptionComponent.kt | 26 +++++ .../chooseoption/ui/Wallet1ChooseOption.kt | 67 +++++++++++++ .../MultiWalletCreateWalletComponent.kt | 5 +- .../model/MultiWalletCreateWalletModel.kt | 31 +++++- .../ui/MultiWalletCreateWallet.kt | 51 ++++++++-- .../ui/state/MultiWalletCreateWalletUM.kt | 4 +- .../impl/model/OnboardingMultiWalletState.kt | 13 ++- .../v2/multiwallet/impl/model/Utils.kt | 2 + .../impl/ui/OnboardingMultiWallet.kt | 10 +- .../v2/multiwallet/impl/ui/WalletArtwork.kt | 23 +++++ 15 files changed, 354 insertions(+), 169 deletions(-) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/Wallet1ChooseOptionComponent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt 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 846e1f0d53..97c2f1e5df 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 @@ -10,6 +10,7 @@ import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackA import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState import com.arkivanov.decompose.router.stack.* import com.arkivanov.decompose.value.Value +import com.arkivanov.essenty.instancekeeper.getOrCreateSimple import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel @@ -19,10 +20,13 @@ import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletCo 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.chooseoption.Wallet1ChooseOptionComponent 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.model.OnboardingMultiWalletState.Step.* import com.tangem.features.onboarding.v2.multiwallet.impl.ui.OnboardingMultiWallet +import com.tangem.features.onboarding.v2.multiwallet.impl.ui.WalletArtworksState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -35,6 +39,18 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor private val model: OnboardingMultiWalletModel = getOrCreateModel(params) private val stackNavigation = StackNavigation() + private val artworksState = instanceKeeper.getOrCreateSimple { + MutableStateFlow( + when (model.state.value.currentStep) { + CreateWallet -> WalletArtworksState.Folded + ChooseBackupOption -> WalletArtworksState.Fan + SeedPhrase -> WalletArtworksState.Hidden + AddBackupDevice -> WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.First) + FinishBackup -> WalletArtworksState.Stack() + Done -> error("Done state should not be used as starting state") + }, + ) + } override val innerNavigation: InnerNavigation = object : InnerNavigation { override val state = MutableStateFlow( @@ -47,50 +63,84 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor } } - 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 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( + CreateWallet -> MultiWalletCreateWalletComponent( context = childContext, params = MultiWalletChildParams( multiWalletState = model.state, parentParams = params, ), - onDone = ::onStepDone, + onNextStep = ::handleNavigationEvent, ) - OnboardingMultiWalletState.Step.AddBackupDevice -> MultiWalletBackupComponent( + ChooseBackupOption -> Wallet1ChooseOptionComponent( + context = childContext, + onNextStep = ::handleNavigationEvent, + ) + SeedPhrase -> TODO() + AddBackupDevice -> MultiWalletBackupComponent( context = childContext, params = MultiWalletChildParams( multiWalletState = model.state, parentParams = params, ), - onDone = ::onStepDone, + onEvent = ::handleBackupComponentEvent, ) - OnboardingMultiWalletState.Step.FinishBackup -> TODO() - OnboardingMultiWalletState.Step.Done -> TODO() + FinishBackup -> TODO() + Done -> TODO() } } - private fun onStepDone() { - when (model.state.value.currentStep) { - OnboardingMultiWalletState.Step.CreateWallet -> { - stackNavigation.push(OnboardingMultiWalletState.Step.AddBackupDevice) - // innerNavigation.state.value TODO + fun handleNavigationEvent(nextStep: OnboardingMultiWalletState.Step) { + when (nextStep) { + ChooseBackupOption -> { + artworksState.value = WalletArtworksState.Fan + } + SeedPhrase -> { + artworksState.value = WalletArtworksState.Hidden + } + AddBackupDevice -> { + artworksState.value = WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.First) + } + FinishBackup -> { + } + Done -> { + // TODO + } + else -> return + } + stackNavigation.push(nextStep) + } + + fun handleBackupComponentEvent(event: MultiWalletBackupComponent.Event) { + when (event) { + MultiWalletBackupComponent.Event.Done -> { + // TODO navigate to finalize + } + MultiWalletBackupComponent.Event.OneDeviceAdded -> { + artworksState.value = WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.Second) + } + MultiWalletBackupComponent.Event.TwoDeviceAdded -> { + artworksState.value = WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.Third) } - OnboardingMultiWalletState.Step.AddBackupDevice -> TODO() - OnboardingMultiWalletState.Step.FinishBackup -> TODO() - OnboardingMultiWalletState.Step.Done -> TODO() } } @@ -98,10 +148,12 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor override fun Content(modifier: Modifier) { val stackState by childStack.subscribeAsState() val state by model.uiState.collectAsStateWithLifecycle() + val artworksState by artworksState.collectAsStateWithLifecycle() OnboardingMultiWallet( state = state, modifier = modifier, + artworksState = artworksState, childContent = { mdfr -> Children( stack = stackState, 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 f68f4741b3..a60cfcb3fa 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 @@ -18,7 +18,7 @@ import kotlinx.coroutines.launch class MultiWalletBackupComponent( context: AppComponentContext, params: MultiWalletChildParams, - onDone: () -> Unit, + onEvent: (event: Event) -> Unit, ) : AppComponentContext by context, MultiWalletChildComponent { private val model: MultiWalletBackupModel = getOrCreateModel(params) @@ -29,7 +29,7 @@ class MultiWalletBackupComponent( ) componentScope.launch { - model.onDoneFlow.collect { onDone() } + model.eventFlow.collect(onEvent) } } @@ -42,4 +42,8 @@ class MultiWalletBackupComponent( state = state, ) } + + enum class Event { + Done, OneDeviceAdded, TwoDeviceAdded + } } \ 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 66ffa8ff7f..59ea93d2be 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 @@ -11,6 +11,7 @@ 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.MultiWalletBackupComponent 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 @@ -45,35 +46,31 @@ class MultiWalletBackupModel @Inject constructor( private val _uiState = MutableStateFlow( when (scanResponse.productType) { - ProductType.Wallet -> getWallet1State() + ProductType.Wallet, ProductType.Wallet2, ProductType.Ring, - -> MultiWalletBackupUM.Wallet2( - isRing = scanResponse.productType == ProductType.Ring, - backupAdded = false, // backupService.hasIncompletedBackup, - onAddBackupClick = {}, - onFinalizeButtonClick = {}, - ) + -> getInitState() else -> error("Type: ${scanResponse.productType.name} is not supported!") }, ) val uiState: StateFlow = _uiState - val onDoneFlow = MutableSharedFlow() + val eventFlow = MutableSharedFlow() - private fun getWallet1State(): MultiWalletBackupUM.Wallet1 { + private fun getInitState(): MultiWalletBackupUM { return when (backupService.currentState) { BackupService.State.Preparing -> { - MultiWalletBackupUM.Wallet1( + MultiWalletBackupUM( title = resourceReference(R.string.onboarding_title_no_backup_cards), bodyText = resourceReference(R.string.onboarding_subtitle_no_backup_cards), - showFinalizeButton = false, + finalizeButtonEnabled = false, addBackupButtonEnabled = true, addBackupButtonLoading = false, - artworkNumberOfBackupCards = 0, - onAddBackupClick = ::startBackupWallet1, + onAddBackupClick = ::startBackupWallet, onFinalizeButtonClick = {}, - onSkipButtonClick = { onDoneFlow.tryEmit(Unit) }, + onSkipButtonClick = { + // eventFlow.tryEmit(Unit) + }, ) } is BackupService.State.FinalizingBackupCard -> TODO() @@ -82,7 +79,7 @@ class MultiWalletBackupModel @Inject constructor( } } - private fun startBackupWallet1() { + private fun startBackupWallet() { backupService.discardSavedBackup() val primaryCard = scanResponse.primaryCard @@ -95,35 +92,37 @@ class MultiWalletBackupModel @Inject constructor( } } - private fun setNumberOfBackupCardsWallet1(number: Int) { - // set state for Wallet1 for adding backup cards and disable button if there is more than 2 backup cards + private fun setNumberOfBackupCards(number: Int) { + // set state 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 -> { + modelScope.launch { + eventFlow.emit(MultiWalletBackupComponent.Event.OneDeviceAdded) + } 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, + finalizeButtonEnabled = true, ) } 2 -> { + modelScope.launch { + eventFlow.emit(MultiWalletBackupComponent.Event.TwoDeviceAdded) + } st.copy( title = resourceReference(R.string.onboarding_title_two_backup_cards), bodyText = resourceReference(R.string.onboarding_subtitle_two_backup_cards), - artworkNumberOfBackupCards = 2, addBackupButtonEnabled = false, + finalizeButtonEnabled = true, ) } else -> st.copy(addBackupButtonEnabled = false) @@ -132,7 +131,7 @@ class MultiWalletBackupModel @Inject constructor( } private fun addBackupCardWithService() { - updateWallet1State { st -> + _uiState.update { st -> st.copy(addBackupButtonLoading = true) } @@ -140,38 +139,37 @@ class MultiWalletBackupModel @Inject constructor( backupService.skipCompatibilityChecks = false cardSdkConfigRepository.sdk.config.filter.cardIdFilter = null - updateWallet1State { st -> + _uiState.update { 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) + setNumberOfBackupCards(state.value.backupCardsNumber) } is CompletionResult.Failure -> { when (val error = result.error) { is TangemSdkError.BackupFailedNotEmptyWallets -> { - updateWallet1State { st -> + _uiState.update { st -> st.copy( dialog = resetBackupCardDialog( onReset = { resetBackupCard(cardId = error.cardId) }, - onDismiss = { updateWallet1State { it.copy(dialog = null) } }, + onDismiss = { _uiState.update { it.copy(dialog = null) } }, ), ) } } is TangemSdkError.IssuerSignatureLoadingFailed -> { - updateWallet1State { st -> + _uiState.update { st -> st.copy( dialog = backupCardAttestationFailedDialog( - onDismiss = { updateWallet1State { it.copy(dialog = null) } }, + onDismiss = { _uiState.update { it.copy(dialog = null) } }, ), ) } @@ -191,11 +189,4 @@ class MultiWalletBackupModel @Inject constructor( ) } } - - 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/MultiWalletBackup.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt index a8a1e22522..9bf7b55ef5 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 @@ -24,10 +24,7 @@ fun MultiWalletBackup(state: MultiWalletBackupUM, modifier: Modifier = Modifier) .fillMaxSize() .navigationBarsPadding(), ) { - when (state) { - is MultiWalletBackupUM.Wallet1 -> Wallet1Content(state) - is MultiWalletBackupUM.Wallet2 -> TODO() - } + Content(state) } val dialog = state.dialog @@ -54,7 +51,7 @@ fun MultiWalletBackup(state: MultiWalletBackupUM, modifier: Modifier = Modifier) } @Composable -private fun ColumnScope.Wallet1Content(state: MultiWalletBackupUM.Wallet1) { +private fun ColumnScope.Content(state: MultiWalletBackupUM) { Column( Modifier .fillMaxWidth() @@ -62,8 +59,6 @@ private fun ColumnScope.Wallet1Content(state: MultiWalletBackupUM.Wallet1) { .weight(1f), horizontalAlignment = Alignment.CenterHorizontally, ) { - // artwork - Text( text = state.title.resolveReference(), style = TangemTheme.typography.h2, @@ -81,45 +76,25 @@ private fun ColumnScope.Wallet1Content(state: MultiWalletBackupUM.Wallet1) { ) } - 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, - ) + 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, - ) - - SecondaryButton( - modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) - .fillMaxWidth(), - text = stringResource(R.string.onboarding_button_skip_backup), - onClick = state.onSkipButtonClick, - ) - } + PrimaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = stringResource(R.string.onboarding_button_finalize_backup), + enabled = state.finalizeButtonEnabled, + onClick = state.onFinalizeButtonClick, + ) } @Preview @@ -127,25 +102,10 @@ private fun ColumnScope.Wallet1Content(state: MultiWalletBackupUM.Wallet1) { private fun PreviewWallet1() { TangemThemePreview { MultiWalletBackup( - state = MultiWalletBackupUM.Wallet1( + state = MultiWalletBackupUM( title = stringReference("Title"), bodyText = stringReference("Body body body"), ), ) } -} - -@Preview -@Composable -private fun PreviewWallet2() { - TangemThemePreview { - MultiWalletBackup( - state = MultiWalletBackupUM.Wallet2( - isRing = false, - backupAdded = 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 index cb3351c3db..96cc144862 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,37 +1,21 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state -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 MultiWalletBackupUM( + val title: TextReference = stringReference(""), + val bodyText: TextReference = stringReference(""), + val addBackupButtonEnabled: Boolean = true, + val addBackupButtonLoading: Boolean = false, + val finalizeButtonEnabled: Boolean = false, + val onAddBackupClick: () -> Unit = {}, + val onFinalizeButtonClick: () -> Unit = {}, + val onSkipButtonClick: () -> Unit = {}, + val dialog: MultiWalletBackupUMDialog? = null, +) data class MultiWalletBackupUMDialog( val title: TextReference, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/Wallet1ChooseOptionComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/Wallet1ChooseOptionComponent.kt new file mode 100644 index 0000000000..11b7cacc0e --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/Wallet1ChooseOptionComponent.kt @@ -0,0 +1,26 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.ui.Wallet1ChooseOption +import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState + +class Wallet1ChooseOptionComponent( + context: AppComponentContext, + private val onNextStep: (OnboardingMultiWalletState.Step) -> Unit, +) : AppComponentContext by context, MultiWalletChildComponent { + + @Composable + override fun Content(modifier: Modifier) { + Wallet1ChooseOption( + onBackupClick = { + onNextStep(OnboardingMultiWalletState.Step.AddBackupDevice) + }, + onScipClick = { + onNextStep(OnboardingMultiWalletState.Step.Done) + }, + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt new file mode 100644 index 0000000000..d2aa92ed93 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt @@ -0,0 +1,67 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +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.TangemTheme +import com.tangem.features.onboarding.v2.impl.R + +@Composable +fun Wallet1ChooseOption(onScipClick: () -> Unit, onBackupClick: () -> Unit, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxSize() + .navigationBarsPadding(), + verticalArrangement = Arrangement.Bottom, + ) { + // val pagerState = rememberPagerState(pageCount = { 10 }) // TODO [REDACTED_TASK_KEY] + + Column( + Modifier + .fillMaxWidth() + .padding(start = 32.dp, end = 32.dp, bottom = 24.dp) + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResource(R.string.onboarding_create_wallet_header), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 16.dp), + ) + + Text( + text = stringResource(R.string.onboarding_create_wallet_body), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 12.dp), + ) + } + + 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_backup_now), + onClick = onBackupClick, + ) + + SecondaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = stringResource(R.string.onboarding_button_skip_backup), + onClick = onScipClick, + ) + } +} \ 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 index 713eb54a50..0ab365e362 100644 --- 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 @@ -12,12 +12,13 @@ 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.createwallet.model.MultiWalletCreateWalletModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.MultiWalletCreateWallet +import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState import kotlinx.coroutines.launch class MultiWalletCreateWalletComponent( context: AppComponentContext, params: MultiWalletChildParams, - onDone: () -> Unit, + onNextStep: (OnboardingMultiWalletState.Step) -> Unit, ) : AppComponentContext by context, MultiWalletChildComponent { private val model: MultiWalletCreateWalletModel = getOrCreateModel(params) @@ -28,7 +29,7 @@ class MultiWalletCreateWalletComponent( ) componentScope.launch { - model.onDone.collect { onDone() } + model.onDone.collect(onNextStep) } } 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 e32cbde7d2..88bb4abd0d 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,12 +5,15 @@ 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.CardRepository 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.impl.R 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.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState import com.tangem.sdk.api.TangemSdkManager import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableSharedFlow @@ -36,15 +39,29 @@ class MultiWalletCreateWalletModel @Inject constructor( private val _uiState = MutableStateFlow( MultiWalletCreateWalletUM( + title = if (params.parentParams.withSeedPhraseFlow) { + resourceReference(R.string.onboarding_create_wallet_options_title) + } else { + resourceReference(R.string.onboarding_create_wallet_header) + }, + bodyText = if (params.parentParams.withSeedPhraseFlow) { + resourceReference(R.string.onboarding_create_wallet_options_message) + } else { + resourceReference(R.string.onboarding_create_wallet_body) + }, onCreateWalletClick = { createWallet(false) }, - showSeedPhraseOption = params.parentParams.withSeedPhraseFlow, - onOtherOptionsClick = { /* navigate */ }, + showOtherOptionsButton = params.parentParams.withSeedPhraseFlow, + onOtherOptionsClick = { + modelScope.launch { + onDone.emit(OnboardingMultiWalletState.Step.SeedPhrase) + } + }, dialog = null, ), ) val uiState: StateFlow = _uiState - val onDone = MutableSharedFlow() + val onDone = MutableSharedFlow() private fun createWallet(shouldReset: Boolean) { modelScope.launch { @@ -66,7 +83,13 @@ class MultiWalletCreateWalletModel @Inject constructor( } cardRepository.startCardActivation(cardId = result.data.card.cardId) - onDone.emit(Unit) + + if (params.parentParams.withSeedPhraseFlow) { + onDone.emit(OnboardingMultiWalletState.Step.AddBackupDevice) + } else { + onDone.emit(OnboardingMultiWalletState.Step.ChooseBackupOption) + } + // TODO // Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) } 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 b20e13c3af..6c881297c3 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,9 +1,12 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.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.BasicDialog @@ -11,6 +14,8 @@ 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.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.createwallet.ui.state.MultiWalletCreateWalletUM @@ -40,6 +45,30 @@ fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier .navigationBarsPadding(), verticalArrangement = Arrangement.Bottom, ) { + Column( + Modifier + .fillMaxWidth() + .padding(start = 32.dp, end = 32.dp, bottom = 24.dp) + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + 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), + ) + } + PrimaryButtonIconEnd( modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) @@ -49,24 +78,28 @@ fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier 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, - ) + if (state.showOtherOptionsButton) { + 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 +@Preview(showBackground = true) @Composable private fun Preview() { TangemThemePreview { MultiWalletCreateWallet( state = MultiWalletCreateWalletUM( + title = stringReference("Title"), + bodyText = stringReference("Body body body"), onCreateWalletClick = {}, - showSeedPhraseOption = true, + showOtherOptionsButton = true, onOtherOptionsClick = {}, dialog = null, ), 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 c761e1f481..59a5a9ace0 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 @@ -3,8 +3,10 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui import com.tangem.core.ui.extensions.TextReference data class MultiWalletCreateWalletUM( + val title: TextReference, + val bodyText: TextReference, + val showOtherOptionsButton: Boolean, val onCreateWalletClick: () -> Unit, - val showSeedPhraseOption: Boolean, val onOtherOptionsClick: () -> Unit, val dialog: Dialog?, ) { 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 0c3ecc83ea..14260553a8 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,18 @@ data class OnboardingMultiWalletState( val currentStep: Step, val currentScanResponse: ScanResponse, ) { + /** + * Wallet1 + * CreateWallet -> ChooseBackupOption -> AddBackupDevice -> FinishBackup -> [Done] + * | + * -> [Done] + * + * Wallet2/Ring + * CreateWallet -> SeedPhrase -> AddBackupDevice -> FinishBackup -> [Done] + * | + * -> AddBackupDevice -> FinishBackup -> [Done] + */ enum class Step { - CreateWallet, AddBackupDevice, FinishBackup, Done + CreateWallet, ChooseBackupOption, SeedPhrase, 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 73573ab2a6..ec2a1c3848 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 @@ -7,6 +7,8 @@ import com.tangem.features.onboarding.v2.impl.R fun screenTitleByStep(step: OnboardingMultiWalletState.Step): TextReference = when (step) { OnboardingMultiWalletState.Step.CreateWallet -> resourceReference(R.string.onboarding_create_wallet_header) + OnboardingMultiWalletState.Step.ChooseBackupOption -> TODO() + OnboardingMultiWalletState.Step.SeedPhrase -> TODO() OnboardingMultiWalletState.Step.AddBackupDevice -> resourceReference(R.string.onboarding_navbar_title_creating_backup) OnboardingMultiWalletState.Step.FinishBackup -> TODO() 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 9a3b0efcec..f26fbb2cd4 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 @@ -17,6 +17,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMul @Suppress("MagicNumber") internal fun OnboardingMultiWallet( state: OnboardingMultiWalletUM, + artworksState: WalletArtworksState, modifier: Modifier = Modifier, childContent: @Composable (Modifier) -> Unit = {}, ) { @@ -32,11 +33,15 @@ internal fun OnboardingMultiWallet( .padding(horizontal = 34.dp) .padding(top = 20.dp) .fillMaxWidth(), - state = WalletArtworksState.Folded, + state = artworksState, ) Box( - modifier = Modifier.weight(.44f), + modifier = if (artworksState == WalletArtworksState.Hidden) { + Modifier.fillMaxSize() + } else { + Modifier.weight(.44f) + }, contentAlignment = Alignment.BottomStart, ) { childContent(Modifier) @@ -50,6 +55,7 @@ private fun Preview() { TangemThemePreview { OnboardingMultiWallet( state = OnboardingMultiWalletUM(artworkUrl = null), + artworksState = WalletArtworksState.Folded, childContent = { md -> Box( modifier = md 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 index fa5c1643a7..2c1b7f784c 100644 --- 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 @@ -1,9 +1,11 @@ @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.Canvas import androidx.compose.foundation.layout.* import androidx.compose.material3.Button import androidx.compose.material3.Text @@ -20,12 +22,15 @@ 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.TangemTheme 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 Hidden : WalletArtworksState() + data object Folded : WalletArtworksState() data object Fan : WalletArtworksState() @@ -54,6 +59,8 @@ sealed class WalletArtworksState { @Composable fun WalletArtworks(url: String?, state: WalletArtworksState, modifier: Modifier = Modifier) { + if (state is WalletArtworksState.Hidden) return + BoxWithConstraints( modifier .heightIn(min = 180.dp) @@ -93,6 +100,21 @@ fun WalletArtworks(url: String?, state: WalletArtworksState, modifier: Modifier } } + val circleColor = TangemTheme.colors.background.secondary + + Canvas( + modifier = Modifier + .align(Alignment.Center) + .size(254.dp) + .fillMaxSize(), + ) { + drawCircle( + color = circleColor, + radius = size.width / 2, + center = center, + ) + } + AnimatedWalletCards( url = url, modifier = Modifier @@ -225,6 +247,7 @@ private fun WalletCard(url: String?, modifier: Modifier = Modifier) { private fun WalletArtworksState.toTransitionSetState(maxWidthDp: Float, maxHeightDp: Float, density: Float) = when (this) { + WalletArtworksState.Hidden -> listOf() is WalletArtworksState.Folded -> listOf( CardsTransitionState( WalletCardTransitionState(), From 3d832ddd3e81027bb8299514e1acdf60073a1cc1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Nov 2024 17:01:35 +0300 Subject: [PATCH 40/99] Updated on 2026-08-14 --- .../common/ui/notifications/NotificationUM.kt | 14 +++++++++++ data/tokens/build.gradle.kts | 1 + .../DefaultCurrencyChecksRepository.kt | 23 +++++++++++++++++++ .../DefaultWalletManagersFacade.kt | 23 +++---------------- .../walletmanager/WalletManagersFacade.kt | 4 ++-- .../domain/walletmanager/model/RentData.kt | 11 +++++++++ .../model/warnings/CryptoCurrencyCheck.kt | 1 + .../domain/tokens/GetCurrencyCheckUseCase.kt | 4 ++++ .../tokens/GetCurrencyWarningsUseCase.kt | 2 +- .../repository/CurrencyChecksRepository.kt | 12 ++++++++++ .../state/confirm/SendNotificationFactory.kt | 14 +++++++++++ .../presentation/ui/common/Notifications.kt | 2 +- .../viewmodel/StakingViewModel.kt | 1 + 13 files changed, 88 insertions(+), 24 deletions(-) create mode 100644 domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/RentData.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt index 858fdccb46..83636c2a98 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt @@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.shorted +import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import java.math.BigDecimal sealed class NotificationUM(val config: NotificationConfig) { @@ -307,4 +308,17 @@ sealed class NotificationUM(val config: NotificationConfig) { ), ) } + + sealed interface Solana { + + data class RentInfo( + private val rentInfo: CryptoCurrencyWarning.Rent, + ) : Info( + title = TextReference.Res(R.string.warning_rent_fee_title), + subtitle = TextReference.Res( + id = R.string.warning_solana_rent_fee_message, + formatArgs = wrappedList(rentInfo.rent, rentInfo.exemptionAmount), + ), + ) + } } \ No newline at end of file diff --git a/data/tokens/build.gradle.kts b/data/tokens/build.gradle.kts index 7585dedc01..bf90a79e76 100644 --- a/data/tokens/build.gradle.kts +++ b/data/tokens/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.txhistory.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.staking.models) /** Project - Data */ implementation(projects.core.datasource) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt index a62d70a3f8..afa7be79cc 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -6,13 +6,17 @@ import com.tangem.blockchain.common.MinimumSendAmountProvider import com.tangem.blockchain.common.ReserveAmountProvider import com.tangem.blockchain.common.UtxoAmountLimitProvider import com.tangem.data.tokens.converters.UtxoConverter +import com.tangem.domain.staking.model.stakekit.YieldBalance +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CurrencyAmount import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit +import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.extensions.isZero import kotlinx.coroutines.withContext import java.math.BigDecimal @@ -118,4 +122,23 @@ internal class DefaultCurrencyChecksRepository( } return utxoAmount?.let(UtxoConverter()::convert) } + + override suspend fun getRentInfoWarning( + userWalletId: UserWalletId, + currencyStatus: CryptoCurrencyStatus, + balanceAfterTransaction: BigDecimal?, + ): CryptoCurrencyWarning.Rent? { + val rentData = walletManagersFacade.getRentInfo(userWalletId, currencyStatus.currency.network) ?: return null + val balanceValue = currencyStatus.value as? CryptoCurrencyStatus.Loaded ?: return null + val stakingTotalBalance = + (balanceValue.yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance() ?: BigDecimal.ZERO + val amount = balanceAfterTransaction ?: balanceValue.amount + return when { + amount.isZero() && stakingTotalBalance.isZero() -> null + amount < rentData.exemptionAmount && stakingTotalBalance.isZero() -> { + CryptoCurrencyWarning.Rent(rentData.rent, rentData.exemptionAmount) + } + else -> null + } + } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 5a531976c7..a684f579c9 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -25,11 +25,11 @@ import com.tangem.domain.common.util.hasDerivation import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network -import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryState +import com.tangem.domain.walletmanager.model.RentData import com.tangem.domain.walletmanager.model.SmartContractMethod import com.tangem.domain.walletmanager.model.TokenInfo import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult @@ -413,7 +413,7 @@ class DefaultWalletManagersFacade( return manager?.wallet?.addresses.orEmpty() } - override suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): CryptoCurrencyWarning.Rent? { + override suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): RentData? { val manager = getOrCreateWalletManager( userWalletId = userWalletId, network = network, @@ -422,24 +422,7 @@ class DefaultWalletManagersFacade( return when (val result = manager.minimalBalanceForRentExemption()) { is Result.Success -> { - val balance = manager.wallet.fundsAvailable(AmountType.Coin) - val outgoingTxs = manager.wallet.recentTransactions - .filter { - it.sourceAddress == manager.wallet.address && - it.amount.type == AmountType.Coin && - it.status != TransactionStatus.Confirmed - } - - val rentExempt = result.data - val setRent = if (outgoingTxs.isEmpty()) { - balance < rentExempt - } else { - val outgoingAmount = outgoingTxs.sumOf { it.amount.value ?: BigDecimal.ZERO } - val rest = balance.minus(outgoingAmount) - balance < rest - } - - if (setRent) CryptoCurrencyWarning.Rent(manager.rentAmount(), rentExempt) else null + RentData(manager.rentAmount(), result.data) } is Result.Failure -> null } diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index 2f6317a316..6031b2300b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -12,11 +12,11 @@ import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network -import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.transaction.models.AssetRequirementsCondition import com.tangem.domain.txhistory.models.PaginationWrapper import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryState +import com.tangem.domain.walletmanager.model.RentData import com.tangem.domain.walletmanager.model.TokenInfo import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult import com.tangem.domain.wallets.models.UserWalletId @@ -140,7 +140,7 @@ interface WalletManagersFacade { * @param userWalletId selected wallet id * @param network network of currency */ - suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): CryptoCurrencyWarning.Rent? + suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): RentData? @Deprecated("Will be removed in future") fun getAll(userWalletId: UserWalletId): Flow> diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/RentData.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/RentData.kt new file mode 100644 index 0000000000..05973c0c00 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/RentData.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.walletmanager.model + +import java.math.BigDecimal + +/** + * Represents wallet blockchain rent + * @param rent Amount that will be charged in overtime if the blockchain does not have an amount greater than + * the [exemptionAmount] + * @param exemptionAmount Amount that should be on the blockchain balance not to pay rent + */ +data class RentData(val rent: BigDecimal, val exemptionAmount: BigDecimal) \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt index dae8238b02..906c1ff0a4 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt @@ -10,4 +10,5 @@ data class CryptoCurrencyCheck( val existentialDeposit: BigDecimal?, val utxoAmountLimit: UtxoAmountLimit?, val isAccountFunded: Boolean, + val rentWarning: CryptoCurrencyWarning.Rent?, ) \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt index ba9e837248..8de4b3ea7e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt @@ -18,6 +18,7 @@ class GetCurrencyCheckUseCase( currencyStatus: CryptoCurrencyStatus, amount: BigDecimal?, fee: BigDecimal?, + balanceAfterTransaction: BigDecimal?, recipientAddress: String? = null, ): CryptoCurrencyCheck { return withContext(dispatchers.io) { @@ -26,6 +27,8 @@ class GetCurrencyCheckUseCase( val reserveAmount = currencyChecksRepository.getReserveAmount(userWalletId, network) val minimumSendAmount = currencyChecksRepository.getMinimumSendAmount(userWalletId, network) val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, network) + val rentWarning = + currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus, balanceAfterTransaction) val isAccountFunded = recipientAddress?.let { currencyChecksRepository.checkIfAccountFunded( userWalletId, @@ -51,6 +54,7 @@ class GetCurrencyCheckUseCase( existentialDeposit = existentialDeposit, utxoAmountLimit = utxoAmountLimit, isAccountFunded = isAccountFunded, + rentWarning = rentWarning, ) } } 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 9118e3098d..f52bcea470 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 @@ -58,7 +58,7 @@ class GetCurrencyWarningsUseCase( derivationPath = derivationPath, isSingleWalletWithTokens = isSingleWalletWithTokens, ), - flowOf(walletManagersFacade.getRentInfo(userWalletId, currency.network)), + flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus, null)), flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)), flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)), getSwapPromoNotificationWarning( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt index 4ff37c6420..726e942b37 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt @@ -1,8 +1,10 @@ package com.tangem.domain.tokens.repository +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CurrencyAmount import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit +import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.wallets.models.UserWalletId import java.math.BigDecimal @@ -39,4 +41,14 @@ interface CurrencyChecksRepository { amount: BigDecimal, fee: BigDecimal, ): UtxoAmountLimit? + + /** + * Checks if need warning for Solana rent + * @param balanceAfterTransaction if non null, checks with balance remains after transaction + */ + suspend fun getRentInfoWarning( + userWalletId: UserWalletId, + currencyStatus: CryptoCurrencyStatus, + balanceAfterTransaction: BigDecimal?, + ): CryptoCurrencyWarning.Rent? } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 9975712433..21604ac0b0 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -22,6 +22,7 @@ import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase import com.tangem.domain.tokens.GetCurrencyCheckUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck +import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase import com.tangem.domain.utils.convertToSdkAmount @@ -93,12 +94,15 @@ internal class SendNotificationFactory( val feeError = (feeState.feeSelectorState as? FeeSelectorState.Error)?.error val recipientAddress = state.recipientState?.addressTextField?.value + val statusValue = cryptoCurrencyStatus.value as? CryptoCurrencyStatus.Loaded + val balanceAfterTransaction = statusValue?.let { it.amount - sendingAmount - feeValue } val currencyCheck = getCurrencyCheckUseCase( userWalletId = userWalletId, currencyStatus = cryptoCurrencyStatus, amount = sendingAmount, fee = feeValue, recipientAddress = recipientAddress, + balanceAfterTransaction = balanceAfterTransaction, ) buildList { addErrorNotifications( @@ -221,6 +225,11 @@ internal class SendNotificationFactory( network = cryptoCurrencyStatus.currency.network, ).leftOrNull() } + + addRentExemptionNotification( + rentWarning = currencyCheck.rentWarning, + ) + addExistentialWarningNotification( existentialDeposit = currencyCheck.existentialDeposit, feeAmount = feeState.fee?.amount?.value.orZero(), @@ -303,4 +312,9 @@ internal class SendNotificationFactory( add(NotificationUM.Warning.TooHigh(diff)) } } + + private fun MutableList.addRentExemptionNotification(rentWarning: CryptoCurrencyWarning.Rent?) { + if (rentWarning == null) return + add(NotificationUM.Solana.RentInfo(rentWarning)) + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt index b6d93d00c8..501c61649f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/common/Notifications.kt @@ -44,7 +44,7 @@ internal fun LazyListScope.notifications( is NotificationUM.Warning, -> null is NotificationUM.Error -> TangemTheme.colors.icon.warning - is NotificationUM.Info -> null + is NotificationUM.Info -> TangemTheme.colors.icon.accent }, isEnabled = !isClickDisabled, ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index b58a5d7dfb..b28cfd94ad 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -638,6 +638,7 @@ internal class StakingViewModel @Inject constructor( currencyStatus = cryptoCurrencyStatus, amount = amount, fee = fee, + balanceAfterTransaction = null, // ignore this in staking ) stateController.update( AddStakingNotificationsTransformer( From 4b3d59fa0f6c808fc97af3b3915f46c0c69e015f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Nov 2024 10:40:45 +0300 Subject: [PATCH 41/99] Updated on 2026-08-14 --- .../src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt | 1 - 1 file changed, 1 deletion(-) 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 46c633a3c9..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 @@ -416,5 +416,4 @@ private val excludedBlockchains = listOf( Blockchain.Unknown, Blockchain.Nexa, Blockchain.NexaTestnet, - Blockchain.Xodex, ) \ No newline at end of file From e6c8647f0337522ef863cef85cb44ed5efbf32ff Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Nov 2024 10:52:54 +0300 Subject: [PATCH 42/99] Updated on 2026-08-14 --- .../DefaultOnboardingMultiWalletComponent.kt | 29 ++- .../MultiWalletSeedPhraseComponent.kt | 44 ++++ .../model/MultiWalletSeedPhraseModel.kt | 56 +++++ .../child/seedphrase/model/SeedPhraseState.kt | 6 + .../seedphrase/ui/MultiWalletSeedPhrase.kt | 37 ++++ .../ui/MultiWalletSeedPhraseImport.kt | 201 +++++++++++++++++ .../ui/MultiWalletSeedPhraseStart.kt | 158 +++++++++++++ .../ui/MultiWalletSeedPhraseWords.kt | 208 ++++++++++++++++++ .../ui/MultiWalletSeedPhraseWordsCheck.kt | 122 ++++++++++ .../ui/PassphraseInfoBottomSheet.kt | 90 ++++++++ .../ui/state/MultiWalletSeedPhraseUM.kt | 62 ++++++ .../utils/InvalidWordsColorTransformation.kt | 77 +++++++ .../v2/multiwallet/impl/di/ComponentModule.kt | 6 + .../impl/ui/OnboardingMultiWallet.kt | 63 ++++-- 14 files changed, 1132 insertions(+), 27 deletions(-) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseStart.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/PassphraseInfoBottomSheet.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/state/MultiWalletSeedPhraseUM.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/utils/InvalidWordsColorTransformation.kt 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 97c2f1e5df..876f7de961 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 @@ -4,7 +4,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.decompose.FaultyDecomposeApi import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children +import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.fade 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 @@ -22,6 +24,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWalletBackupComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.Wallet1ChooseOptionComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.MultiWalletSeedPhraseComponent 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.model.OnboardingMultiWalletState.Step.* @@ -30,6 +33,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.ui.WalletArtworksState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor( @@ -44,7 +48,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor when (model.state.value.currentStep) { CreateWallet -> WalletArtworksState.Folded ChooseBackupOption -> WalletArtworksState.Fan - SeedPhrase -> WalletArtworksState.Hidden + SeedPhrase -> WalletArtworksState.Folded AddBackupDevice -> WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.First) FinishBackup -> WalletArtworksState.Stack() Done -> error("Done state should not be used as starting state") @@ -95,7 +99,15 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor context = childContext, onNextStep = ::handleNavigationEvent, ) - SeedPhrase -> TODO() + SeedPhrase -> MultiWalletSeedPhraseComponent( + context = childContext, + params = MultiWalletChildParams( + multiWalletState = model.state, + parentParams = params, + ), + onNextStep = ::handleNavigationEvent, + backButtonClickFlow = MutableSharedFlow(), + ) AddBackupDevice -> MultiWalletBackupComponent( context = childContext, params = MultiWalletChildParams( @@ -115,7 +127,6 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor artworksState.value = WalletArtworksState.Fan } SeedPhrase -> { - artworksState.value = WalletArtworksState.Hidden } AddBackupDevice -> { artworksState.value = WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.First) @@ -144,6 +155,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor } } + @OptIn(FaultyDecomposeApi::class) @Composable override fun Content(modifier: Modifier) { val stackState by childStack.subscribeAsState() @@ -154,10 +166,19 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor state = state, modifier = modifier, artworksState = artworksState, + isSeedPhraseState = stackState.active.configuration == SeedPhrase, childContent = { mdfr -> Children( stack = stackState, - animation = stackAnimation(slide()), + animation = stackAnimation( + selector = { one, two, direction -> + if (one.configuration == SeedPhrase || two.configuration == SeedPhrase) { + fade() + } else { + slide() + } + }, + ), ) { it.instance.Content(mdfr) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt new file mode 100644 index 0000000000..89340f3ab8 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt @@ -0,0 +1,44 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase + +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.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.seedphrase.model.MultiWalletSeedPhraseModel +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.MultiWalletSeedPhrase +import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch + +@Suppress("UnusedPrivateMember") +class MultiWalletSeedPhraseComponent( + context: AppComponentContext, + params: MultiWalletChildParams, + onNextStep: (OnboardingMultiWalletState.Step) -> Unit, + backButtonClickFlow: SharedFlow, +) : AppComponentContext by context, MultiWalletChildComponent { + + private val model: MultiWalletSeedPhraseModel = getOrCreateModel() + + init { + componentScope.launch { + backButtonClickFlow.collect { + model.onBack() + } + } + } + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + + MultiWalletSeedPhrase( + 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/seedphrase/model/MultiWalletSeedPhraseModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt new file mode 100644 index 0000000000..5dcc192bb2 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt @@ -0,0 +1,56 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model + +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject + +@ComponentScoped +class MultiWalletSeedPhraseModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, +) : Model() { + + private val state = MutableStateFlow(SeedPhraseState()) + + private val _uiState = MutableStateFlow( + MultiWalletSeedPhraseUM.Start( + onImportSeedPhraseClicked = {}, + onGenerateSeedPhraseClicked = { + openGeneratedSeedPhrase() + }, + onLearnMoreClicked = {}, + ), + ) + + val uiState = _uiState.asStateFlow() + + fun onBack() { + TODO("Not yet implemented") + } + + private fun openGeneratedSeedPhrase() { + // TODO state.value.generatedWords + val words = listOf("word, word") + + _uiState.value = MultiWalletSeedPhraseUM.GenerateSeedPhrase( + words24OptionSelected = false, + words = words.mapIndexed { index, s -> + MultiWalletSeedPhraseUM.GenerateSeedPhrase.MnemonicGridItem(index, s) + }.toImmutableList(), + onWords24OptionSwitch = { + _uiState.update { + if (it !is MultiWalletSeedPhraseUM.GenerateSeedPhrase) return@update it + it.copy(words24OptionSelected = it.words24OptionSelected.not()) + } + state.update { it.copy(words24Option = it.words24Option.not()) } + }, + onContinueClick = { + }, + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt new file mode 100644 index 0000000000..8b854a461c --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt @@ -0,0 +1,6 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model + +data class SeedPhraseState( + val generatedWords: List? = null, + val words24Option: Boolean = false, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt new file mode 100644 index 0000000000..2d220858a2 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt @@ -0,0 +1,37 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.core.tween +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM + +@Composable +fun MultiWalletSeedPhrase(state: MultiWalletSeedPhraseUM, modifier: Modifier = Modifier) { + AnimatedContent( + modifier = modifier.navigationBarsPadding(), + targetState = state, + transitionSpec = { + val direction = if (initialState.order < targetState.order) { + AnimatedContentTransitionScope.SlideDirection.Start + } else { + AnimatedContentTransitionScope.SlideDirection.End + } + + slideIntoContainer(towards = direction, animationSpec = tween()) + .togetherWith(slideOutOfContainer(towards = direction, animationSpec = tween())) + }, + contentKey = { st -> st::class.java }, + label = "animatedContent", + ) { st -> + when (st) { + is MultiWalletSeedPhraseUM.Start -> MultiWalletSeedPhraseStart(state = st) + is MultiWalletSeedPhraseUM.GenerateSeedPhrase -> MultiWalletSeedPhraseWords(state = st) + is MultiWalletSeedPhraseUM.GeneratedWordsCheck -> MultiWalletSeedPhraseWordsCheck(state = st) + is MultiWalletSeedPhraseUM.Import -> MultiWalletSeedPhraseImport(state = st) + } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt new file mode 100644 index 0000000000..532ac00f37 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt @@ -0,0 +1,201 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui + +import androidx.compose.animation.* +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +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.seedphrase.ui.state.MultiWalletSeedPhraseUM +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.utils.InvalidWordsColorTransformation +import kotlinx.collections.immutable.ImmutableList + +@Composable +fun MultiWalletSeedPhraseImport(state: MultiWalletSeedPhraseUM.Import, modifier: Modifier = Modifier) { + Box(modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + ) { + Text( + text = stringResource(id = R.string.onboarding_seed_import_message), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(start = 36.dp, end = 36.dp, top = 24.dp, bottom = 16.dp) + .fillMaxWidth(), + ) + + PhraseBlock( + state = state, + modifier = Modifier.padding(horizontal = 16.dp), + ) + + OutlineTextFieldWithIcon( + modifier = modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + value = state.passPhrase, + onValueChange = state.passPhraseChange, + iconResId = R.drawable.ic_information_24, + iconColor = TangemTheme.colors.icon.informative, + label = stringResource(id = R.string.common_passphrase), + placeholder = stringResource(id = R.string.send_optional_field), + onIconClick = state.onPassphraseInfoClick, + ) + + Box(Modifier.weight(1f)) { + PrimaryButtonIconEnd( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(16.dp) + .fillMaxWidth(), + text = stringResource(id = R.string.common_import), + iconResId = R.drawable.ic_tangem_24, + enabled = state.createWalletEnabled, + showProgress = state.createWalletProgress, + onClick = state.createWalletClick, + ) + } + } + + val keyboard by keyboardAsState() + if (keyboard is Keyboard.Opened) { + SuggestionsBlock( + modifier = Modifier + .align(Alignment.BottomCenter) + .imePadding(), + suggestionsList = state.suggestionsList, + onClick = { index -> state.onSuggestionClick(state.suggestionsList[index]) }, + ) + } + } + + PassphraseInfoBottomSheet(state.infoBottomSheetConfig) +} + +@Composable +private fun PhraseBlock(state: MultiWalletSeedPhraseUM.Import, modifier: Modifier = Modifier) { + val warningColor = TangemTheme.colors.text.warning + val invalidWordsColorTransformation = remember(state.invalidWords, warningColor) { + InvalidWordsColorTransformation( + wordsToBrush = state.invalidWords, + style = SpanStyle(color = warningColor), + ) + } + + Column( + modifier = modifier.fillMaxWidth(), + ) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size142), + value = state.passPhrase, + onValueChange = state.passPhraseChange, + textStyle = TangemTheme.typography.body1, + singleLine = false, + visualTransformation = invalidWordsColorTransformation, + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .height(TangemTheme.dimens.size32), + ) { + if (state.wordsErrorText != null) { + Text( + modifier = Modifier.fillMaxSize(), + text = state.wordsErrorText.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.warning, + ) + } + } + } +} + +@Composable +private fun SuggestionsBlock( + suggestionsList: ImmutableList, + onClick: (Int) -> Unit, + modifier: Modifier = Modifier, +) { + AnimatedVisibility( + modifier = modifier, + enter = fadeIn() + slideIn(initialOffset = { IntOffset(x = 200, y = 0) }), + exit = slideOut(targetOffset = { IntOffset(x = -200, y = 0) }) + fadeOut(), + visible = suggestionsList.isNotEmpty(), + ) { + LazyRow( + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.size16), + ) { + items(suggestionsList.size) { index -> + SuggestionButton( + modifier = Modifier.rowPadding( + index = index, + rowSize = suggestionsList.size, + outSide = TangemTheme.dimens.size0, + inSide = TangemTheme.dimens.size4, + ), + text = suggestionsList[index], + onClick = { onClick(index) }, + ) + } + } + } +} + +@Composable +private fun SuggestionButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) { + Box(modifier = modifier.clickable(onClick = onClick)) { + Notifier( + text = text, + backgroundColor = TangemTheme.colors.icon.primary1, + textColor = TangemTheme.colors.text.primary2, + ) + } +} + +private fun Modifier.rowPadding(index: Int, rowSize: Int, outSide: Dp, inSide: Dp): Modifier = when (index) { + 0 -> this.padding(start = outSide, end = inSide) + rowSize - 1 -> this.padding(start = inSide, end = outSide) + else -> this.padding(horizontal = inSide) +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview( + alwaysShowBottomSheets = false, + ) { + MultiWalletSeedPhraseImport( + state = MultiWalletSeedPhraseUM.Import( + wordsErrorText = stringReference("Error"), + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseStart.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseStart.kt new file mode 100644 index 0000000000..aff9adb6f1 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseStart.kt @@ -0,0 +1,158 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +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.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.LineBreak +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.SecondaryButton +import com.tangem.core.ui.components.SpacerH32 +import com.tangem.core.ui.components.SpacerW8 +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.seedphrase.ui.state.MultiWalletSeedPhraseUM + +@Composable +internal fun MultiWalletSeedPhraseStart(state: MultiWalletSeedPhraseUM.Start, modifier: Modifier = Modifier) { + Column( + modifier.padding(top = 48.dp), + ) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .weight(1f), + verticalArrangement = Arrangement.spacedBy(28.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + modifier = Modifier.size(56.dp), + imageVector = ImageVector.vectorResource(R.drawable.ic_onboarding_text_edit_56), + contentDescription = null, + ) + Box( + modifier = Modifier + .background( + color = TangemTheme.colors.icon.warning.copy(alpha = 0.12f), + shape = RoundedCornerShape(TangemTheme.dimens.radius8), + ), + ) { + Text( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.size12, vertical = TangemTheme.dimens.size4), + text = stringResource(id = R.string.onboarding_seed_phrase_intro_legacy), + color = TangemTheme.colors.text.warning, + style = TangemTheme.typography.body1, + ) + } + BodyContent(state) + } + SpacerH32() + Buttons(state) + } +} + +@Composable +private fun BodyContent(state: MultiWalletSeedPhraseUM.Start, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + text = stringResource(id = R.string.onboarding_seed_intro_title), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + ) + Text( + text = stringResource(id = R.string.onboarding_seed_intro_message), + style = TangemTheme.typography.body1 + .copy(lineBreak = LineBreak.Paragraph), + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(horizontal = 32.dp) + .fillMaxWidth(), + ) + ReadMoreBlock(state) + } +} + +@Composable +private fun ReadMoreBlock(state: MultiWalletSeedPhraseUM.Start, modifier: Modifier = Modifier) { + Box( + modifier = modifier + .fillMaxWidth() + .padding(top = 16.dp), + ) { + val shape = TangemTheme.shapes.roundedCornersLarge + Row( + modifier = Modifier + .align(Alignment.Center) + .clip(shape) + .border( + width = 1.dp, + color = TangemTheme.colors.stroke.primary, + shape = shape, + ) + .clickable(onClick = state.onLearnMoreClicked) + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.Center, + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_top_right_24), + contentDescription = null, + tint = TangemTheme.colors.icon.primary1, + ) + SpacerW8() + Text( + modifier = Modifier.align(Alignment.CenterVertically), + style = TangemTheme.typography.button, + text = stringResource(id = R.string.onboarding_seed_button_read_more), + ) + } + } +} + +@Composable +private fun Buttons(state: MultiWalletSeedPhraseUM.Start, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .imePadding(), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = R.string.onboarding_seed_intro_button_generate), + onClick = state.onGenerateSeedPhraseClicked, + ) + SecondaryButton( + modifier = Modifier + .fillMaxWidth(), + text = stringResource(id = R.string.onboarding_seed_intro_button_import), + onClick = state.onImportSeedPhraseClicked, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview { + MultiWalletSeedPhraseStart( + state = MultiWalletSeedPhraseUM.Start(), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt new file mode 100644 index 0000000000..bba9e89256 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt @@ -0,0 +1,208 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui + +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.platform.LocalLayoutDirection +import androidx.compose.ui.res.pluralStringResource +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.LayoutDirection +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons +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.seedphrase.ui.state.MultiWalletSeedPhraseUM +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM.GenerateSeedPhrase.MnemonicGridItem +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@Composable +fun MultiWalletSeedPhraseWords(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifier: Modifier = Modifier) { + Column(modifier.fillMaxSize()) { + SegmentSeedBlock( + modifier = Modifier.padding(vertical = 20.dp), + state = state, + ) + + TitleBlock(state) + + SeedPhraseGridBlock( + mnemonicGridItems = state.words, + modifier = Modifier + .padding(top = 20.dp, bottom = 92.dp), + ) + + Box(Modifier.weight(1f)) { + PrimaryButton( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + text = stringResource(id = R.string.common_continue), + onClick = state.onContinueClick, + ) + } + } +} + +@Suppress("ComplexCondition", "MagicNumber") +@Composable +private fun SegmentSeedBlock(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifier: Modifier = Modifier) { + SegmentedButtons( + modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing76), + config = persistentListOf(0, 1), + initialSelectedItem = if (state.words24OptionSelected.not()) 0 else 1, + onClick = { + if (state.words24OptionSelected && it == 1 || state.words24OptionSelected.not() && it == 0) { + state.onWords24OptionSwitch() + } + }, + ) { + Text( + text = pluralStringResource( + id = R.plurals.onboarding_seed_generate_words_count, + count = if (it == 0) 12 else 24, + if (it == 0) 12 else 24, + ), + modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing10) + .fillMaxWidth(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + } +} + +@Suppress("MagicNumber") +@Composable +private fun TitleBlock(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .padding(horizontal = TangemTheme.dimens.size36) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + text = stringResource(id = R.string.onboarding_seed_generate_title), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + Text( + text = pluralStringResource( + id = R.plurals.onboarding_seed_generate_message_words_count, + count = if (state.words24OptionSelected.not()) 12 else 24, + if (state.words24OptionSelected.not()) 12 else 24, + ), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Composable +private fun SeedPhraseGridBlock(mnemonicGridItems: ImmutableList, modifier: Modifier = Modifier) { + VerticalGrid( + modifier = modifier + .verticalScroll(rememberScrollState()), + items = mnemonicGridItems, + ) { item -> + Row( + modifier = Modifier.padding(all = TangemTheme.dimens.size8), + verticalAlignment = Alignment.CenterVertically, + ) { + if (LocalLayoutDirection.current == LayoutDirection.Ltr) { + Text( + modifier = Modifier.width(TangemTheme.dimens.size40), + text = "${item.index}.", + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + Text( + text = item.mnemonic, + style = TangemTheme.typography.button, + color = TangemTheme.colors.text.primary1, + ) + } else { + Text( + text = item.mnemonic, + style = TangemTheme.typography.button, + color = TangemTheme.colors.text.primary1, + ) + Text( + modifier = Modifier.width(TangemTheme.dimens.size40), + text = "${item.index}.", + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + } + } + } +} + +@Composable +private inline fun VerticalGrid( + items: ImmutableList, + modifier: Modifier = Modifier, + crossinline content: @Composable (T) -> Unit, +) { + val columnLength = items.size / 2 + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + for (index in 0 until 2) { + Column { + for (i in 0 until columnLength) { + val item = items[index * columnLength + i] + content(item) + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview { + MultiWalletSeedPhraseWords( + state = MultiWalletSeedPhraseUM.GenerateSeedPhrase( + words = persistentListOf( + MnemonicGridItem( + index = 0, + mnemonic = "word1", + ), + MnemonicGridItem( + index = 1, + mnemonic = "word1", + ), + MnemonicGridItem( + index = 2, + mnemonic = "word1", + ), + MnemonicGridItem( + index = 3, + mnemonic = "word1", + ), + MnemonicGridItem( + index = 4, + mnemonic = "word1", + ), + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt new file mode 100644 index 0000000000..8f26de11b8 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt @@ -0,0 +1,122 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui + +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.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.components.OutlineTextField +import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM +import kotlinx.collections.immutable.persistentListOf + +@Composable +fun MultiWalletSeedPhraseWordsCheck( + state: MultiWalletSeedPhraseUM.GeneratedWordsCheck, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + ) { + Text( + text = stringResource(R.string.onboarding_seed_user_validation_title), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(top = 48.dp, start = 36.dp, end = 36.dp, bottom = 16.dp) + .fillMaxWidth(), + ) + Text( + text = stringResource(R.string.onboarding_seed_user_validation_message), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(horizontal = 36.dp) + .fillMaxWidth(), + ) + + Fields( + state = state, + modifier = Modifier + .padding(vertical = 30.dp, horizontal = 16.dp), + ) + + Box( + Modifier.weight(1f), + ) { + PrimaryButtonIconEnd( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(16.dp) + .fillMaxWidth(), + text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), + iconResId = R.drawable.ic_tangem_24, + enabled = state.createWalletButtonEnabled, + showProgress = state.createWalletButtonProgress, + onClick = state.onCreateWalletButtonClick, + ) + } + } +} + +@Composable +private fun Fields(state: MultiWalletSeedPhraseUM.GeneratedWordsCheck, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + state.wordFields.forEach { field -> + OutlineTextField( + value = field.word, + onValueChange = field.onChange, + label = field.index.toString(), + isError = field.error, + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview { + MultiWalletSeedPhraseWordsCheck( + state = MultiWalletSeedPhraseUM.GeneratedWordsCheck( + wordFields = persistentListOf( + MultiWalletSeedPhraseUM.GeneratedWordsCheck.WordField( + index = 2, + word = TextFieldValue(text = "word"), + onChange = {}, + error = false, + ), + MultiWalletSeedPhraseUM.GeneratedWordsCheck.WordField( + index = 7, + word = TextFieldValue(text = "wor"), + onChange = {}, + error = true, + ), + MultiWalletSeedPhraseUM.GeneratedWordsCheck.WordField( + index = 11, + word = TextFieldValue(text = "word"), + onChange = {}, + error = false, + ), + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/PassphraseInfoBottomSheet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/PassphraseInfoBottomSheet.kt new file mode 100644 index 0000000000..fdf30402ea --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/PassphraseInfoBottomSheet.kt @@ -0,0 +1,90 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +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.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryButton +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.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R + +@Composable +fun PassphraseInfoBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + containerColor = TangemTheme.colors.background.primary, + ) { _: TangemBottomSheetConfigContent.Empty -> + PassphraseInfoBottomSheetContent(config.onDismissRequest) + } +} + +@Composable +fun PassphraseInfoBottomSheetContent(onDismiss: () -> Unit) { + Column( + modifier = Modifier + .background(color = TangemTheme.colors.background.primary) + .fillMaxWidth(), + ) { + Icon( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(top = TangemTheme.dimens.size40) + .size(TangemTheme.dimens.size48), + painter = painterResource(id = R.drawable.ic_information_24), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) + + Text( + text = stringResource(id = R.string.common_passphrase), + modifier = Modifier + .padding(top = TangemTheme.dimens.size40) + .align(Alignment.CenterHorizontally), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + ) + + Text( + text = stringResource(id = R.string.onboarding_bottom_sheet_passphrase_description), + modifier = Modifier + .padding(top = TangemTheme.dimens.size16) + .padding(horizontal = TangemTheme.dimens.size24) + .align(Alignment.CenterHorizontally), + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.body2, + textAlign = TextAlign.Center, + ) + + PrimaryButton( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.size16) + .padding(top = TangemTheme.dimens.size40) + .padding(bottom = TangemTheme.dimens.size32) + .fillMaxWidth(), + text = stringResource(id = R.string.common_ok), + onClick = onDismiss, + ) + } +} + +@Preview +@Composable +private fun PassphraseInfoBottomSheetContentPreview() { + TangemThemePreview { + PassphraseInfoBottomSheetContent({ }) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/state/MultiWalletSeedPhraseUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/state/MultiWalletSeedPhraseUM.kt new file mode 100644 index 0000000000..84e5b22628 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/state/MultiWalletSeedPhraseUM.kt @@ -0,0 +1,62 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state + +import androidx.compose.runtime.Immutable +import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@Immutable +sealed class MultiWalletSeedPhraseUM( + val order: Int, +) { + + data class Start( + val onLearnMoreClicked: () -> Unit = {}, + val onGenerateSeedPhraseClicked: () -> Unit = {}, + val onImportSeedPhraseClicked: () -> Unit = {}, + ) : MultiWalletSeedPhraseUM(order = 0) + + data class GenerateSeedPhrase( + val words24OptionSelected: Boolean = false, + val words: ImmutableList = persistentListOf(), + val onWords24OptionSwitch: () -> Unit = {}, + val onContinueClick: () -> Unit = {}, + ) : MultiWalletSeedPhraseUM(order = 1) { + data class MnemonicGridItem( + val index: Int, + val mnemonic: String, + ) + } + + data class GeneratedWordsCheck( + val wordFields: ImmutableList = persistentListOf(), + val createWalletButtonEnabled: Boolean = false, + val createWalletButtonProgress: Boolean = false, + val onCreateWalletButtonClick: () -> Unit = {}, + ) : MultiWalletSeedPhraseUM(order = 2) { + data class WordField( + val index: Int, + val word: TextFieldValue, + val error: Boolean, + val onChange: (TextFieldValue) -> Unit, + ) + } + + data class Import( + val words: TextFieldValue = TextFieldValue(""), + val wordsChange: (TextFieldValue) -> Unit = {}, + val passPhrase: TextFieldValue = TextFieldValue(""), + val passPhraseChange: (TextFieldValue) -> Unit = {}, + val onPassphraseInfoClick: () -> Unit = {}, + val wordsErrorText: TextReference? = null, + val invalidWords: ImmutableList = persistentListOf(), + val createWalletEnabled: Boolean = false, + val createWalletProgress: Boolean = false, + val createWalletClick: () -> Unit = {}, + val suggestionsList: ImmutableList = persistentListOf(), + val onSuggestionClick: (String) -> Unit = {}, + val infoBottomSheetConfig: TangemBottomSheetConfig = TangemBottomSheetConfig.Empty, + ) : MultiWalletSeedPhraseUM(order = 1) +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/utils/InvalidWordsColorTransformation.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/utils/InvalidWordsColorTransformation.kt new file mode 100644 index 0000000000..1650f7cd62 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/utils/InvalidWordsColorTransformation.kt @@ -0,0 +1,77 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.utils + +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +import com.tangem.utils.extensions.isSingleItem +import kotlinx.collections.immutable.ImmutableList + +class InvalidWordsColorTransformation( + private val wordsToBrush: ImmutableList, + private val style: SpanStyle, +) : VisualTransformation { + + override fun filter(text: AnnotatedString): TransformedText { + val plainText = text.text + if (wordsToBrush.isSingleItem() && wordsToBrush.first() == plainText) { + return plainText.annotate().toTransformedText() + } + + val wordList = makeSeparatedWordlist(plainText) + val annotatedString = makeAnnotatedString(wordList) + + return annotatedString.toTransformedText() + } + + private fun makeSeparatedWordlist(plainText: String): List { + val plainTextCharArray = plainText.toCharArray() + val wordlist = mutableListOf() + var wordBuilder = StringBuilder() + + for (index in plainTextCharArray.indices) { + val char = plainTextCharArray[index] + if (char.isWhitespace()) { + if (wordBuilder.isEmpty()) { + wordlist.add(WORD_SEPARATOR) + } else { + wordlist.add(wordBuilder.toString()) + wordBuilder = StringBuilder() + wordlist.add(WORD_SEPARATOR) + } + } else { + if (index == plainTextCharArray.lastIndex) { + wordBuilder.append(char) + wordlist.add(wordBuilder.toString()) + wordBuilder = StringBuilder() + } else { + wordBuilder.append(char) + } + } + } + return wordlist.toList() + } + + private fun makeAnnotatedString(wordList: List): AnnotatedString = buildAnnotatedString { + wordList.forEach { + if (it == WORD_SEPARATOR) { + append(WHITE_SPACE) + } else if (wordsToBrush.contains(it)) { + append(it.annotate()) + } else { + append(it) + } + } + } + + private fun String.annotate(): AnnotatedString = AnnotatedString(this, style) + + private fun AnnotatedString.toTransformedText(): TransformedText = TransformedText(this, OffsetMapping.Identity) + + companion object { + private const val WHITE_SPACE = " " + private const val WORD_SEPARATOR = "_$@-*" + } +} \ 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 363655f367..d1df99ba69 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 @@ -6,6 +6,7 @@ import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletCo 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.child.seedphrase.model.MultiWalletSeedPhraseModel import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel import dagger.Binds import dagger.Module @@ -42,4 +43,9 @@ internal interface ModelModule { @IntoMap @ClassKey(MultiWalletCreateWalletModel::class) fun provideModel3(model: MultiWalletCreateWalletModel): Model + + @Binds + @IntoMap + @ClassKey(MultiWalletSeedPhraseModel::class) + fun provideModel4(model: MultiWalletSeedPhraseModel): Model } \ 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 f26fbb2cd4..c698e7e721 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 @@ -2,8 +2,6 @@ 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 @@ -18,33 +16,32 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMul internal fun OnboardingMultiWallet( state: OnboardingMultiWalletUM, artworksState: WalletArtworksState, + isSeedPhraseState: Boolean, modifier: Modifier = Modifier, childContent: @Composable (Modifier) -> Unit = {}, ) { Column( - modifier = modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()), + modifier = modifier.fillMaxSize(), ) { - WalletArtworks( - url = state.artworkUrl, - modifier = Modifier - .weight(.56f) - .padding(horizontal = 34.dp) - .padding(top = 20.dp) - .fillMaxWidth(), - state = artworksState, - ) + if (isSeedPhraseState.not()) { + WalletArtworks( + url = state.artworkUrl, + modifier = Modifier + .weight(.56f) + .padding(horizontal = 34.dp) + .padding(top = 20.dp) + .fillMaxWidth(), + state = artworksState, + ) - Box( - modifier = if (artworksState == WalletArtworksState.Hidden) { - Modifier.fillMaxSize() - } else { - Modifier.weight(.44f) - }, - contentAlignment = Alignment.BottomStart, - ) { - childContent(Modifier) + Box( + modifier = Modifier.weight(.44f), + contentAlignment = Alignment.BottomStart, + ) { + childContent(Modifier) + } + } else { + childContent(Modifier.fillMaxSize()) } } } @@ -56,6 +53,7 @@ private fun Preview() { OnboardingMultiWallet( state = OnboardingMultiWalletUM(artworkUrl = null), artworksState = WalletArtworksState.Folded, + isSeedPhraseState = false, childContent = { md -> Box( modifier = md @@ -66,4 +64,23 @@ private fun Preview() { }, ) } +} + +@Preview(showBackground = true, widthDp = 360, heightDp = 640) +@Composable +private fun Preview2() { + TangemThemePreview { + OnboardingMultiWallet( + state = OnboardingMultiWalletUM(artworkUrl = null), + artworksState = WalletArtworksState.Folded, + isSeedPhraseState = true, + childContent = { md -> + Box( + modifier = md + .background(Color.DarkGray) + .fillMaxSize(), + ) + }, + ) + } } \ No newline at end of file From 6cc6bcfa4c02e633394459cb33c5940d0e0e0766 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Nov 2024 14:32:47 +0300 Subject: [PATCH 43/99] Updated on 2026-08-14 --- features/onboarding-v2/impl/build.gradle.kts | 2 + .../MultiWalletSeedPhraseComponent.kt | 5 + .../seedphrase/model/MnemonicRepository.kt | 35 ++++++ .../model/MultiWalletSeedPhraseModel.kt | 102 ++++++++++++----- .../child/seedphrase/model/SeedPhraseState.kt | 6 +- .../impl/child/seedphrase/model/Utils.kt | 27 +++++ .../GenerateSeedPhraseUiStateBuilder.kt | 52 +++++++++ .../builder/SeedPhraseCheckUiStateBuilder.kt | 105 ++++++++++++++++++ .../ui/MultiWalletSeedPhraseWords.kt | 54 ++++----- .../impl/model/OnboardingMultiWalletModel.kt | 1 + 10 files changed, 327 insertions(+), 62 deletions(-) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MnemonicRepository.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/Utils.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/GenerateSeedPhraseUiStateBuilder.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/SeedPhraseCheckUiStateBuilder.kt diff --git a/features/onboarding-v2/impl/build.gradle.kts b/features/onboarding-v2/impl/build.gradle.kts index fac172d9d1..112c1b01f6 100644 --- a/features/onboarding-v2/impl/build.gradle.kts +++ b/features/onboarding-v2/impl/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation(projects.core.ui) implementation(projects.core.res) implementation(projects.core.decompose) + implementation(projects.core.navigation) /** Domain */ implementation(projects.domain.models) @@ -51,6 +52,7 @@ dependencies { implementation(deps.compose.ui.tooling) implementation(deps.compose.coil) implementation(deps.decompose.ext.compose) + implementation(deps.androidx.activity.compose) /** Other libraries */ implementation(deps.kotlin.immutable.collections) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt index 89340f3ab8..505018842e 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt @@ -1,5 +1,6 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase +import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -36,6 +37,10 @@ class MultiWalletSeedPhraseComponent( override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() + BackHandler(enabled = true) { + model.onBack() + } + MultiWalletSeedPhrase( modifier = modifier, state = state, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MnemonicRepository.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MnemonicRepository.kt new file mode 100644 index 0000000000..6b6ae2b138 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MnemonicRepository.kt @@ -0,0 +1,35 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model + +import android.content.Context +import com.tangem.crypto.bip39.DefaultMnemonic +import com.tangem.crypto.bip39.EntropyLength +import com.tangem.crypto.bip39.Mnemonic +import com.tangem.crypto.bip39.Wordlist +import com.tangem.sdk.extensions.getWordlist +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class MnemonicRepository @Inject constructor( + @ApplicationContext private val context: Context, +) { + private val wordlist = Wordlist.getWordlist(context) + + val words: Set = wordlist.words.toHashSet() + + fun generateMnemonic(type: MnemonicType = MnemonicType.Words12): Mnemonic = DefaultMnemonic( + entropy = when (type) { + MnemonicType.Words12 -> EntropyLength.Bits128Length + MnemonicType.Words24 -> EntropyLength.Bits256Length + }, + wordlist = wordlist, + ) + + fun generateMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic( + mnemonic = mnemonicString, + wordlist = wordlist, + ) + + enum class MnemonicType { + Words12, Words24, + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt index 5dcc192bb2..c867aaf14c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt @@ -2,9 +2,11 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.mode import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.navigation.url.UrlOpener +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder.GenerateSeedPhraseUiStateBuilder +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder.SeedPhraseCheckUiStateBuilder import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -13,44 +15,92 @@ import javax.inject.Inject @ComponentScoped class MultiWalletSeedPhraseModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, + private val mnemonicRepository: MnemonicRepository, + private val urlOpener: UrlOpener, ) : Model() { private val state = MutableStateFlow(SeedPhraseState()) + private val _uiState = MutableStateFlow(getStartState()) - private val _uiState = MutableStateFlow( - MultiWalletSeedPhraseUM.Start( - onImportSeedPhraseClicked = {}, - onGenerateSeedPhraseClicked = { - openGeneratedSeedPhrase() - }, - onLearnMoreClicked = {}, - ), + private val generateSeedPhraseUiStateBuilder = GenerateSeedPhraseUiStateBuilder( + state = state, + updateUiState = { block -> updateUiStateSpecific(block) }, + onContinue = { openWordsCheck() }, + ) + + private val seedPhraseCheckUiStateBuilder = SeedPhraseCheckUiStateBuilder( + state = state, + currentUiState = { _uiState.value }, + updateUiState = { block -> updateUiStateSpecific(block) }, + importWallet = { }, ) val uiState = _uiState.asStateFlow() fun onBack() { - TODO("Not yet implemented") + when (_uiState.value) { + is MultiWalletSeedPhraseUM.Start -> { + // TODO navigate back + } + is MultiWalletSeedPhraseUM.Import, + is MultiWalletSeedPhraseUM.GenerateSeedPhrase, + -> { + _uiState.value = getStartState() + state.value = SeedPhraseState() + } + is MultiWalletSeedPhraseUM.GeneratedWordsCheck -> { + openGeneratedSeedPhrase() + } + } } - private fun openGeneratedSeedPhrase() { - // TODO state.value.generatedWords - val words = listOf("word, word") - - _uiState.value = MultiWalletSeedPhraseUM.GenerateSeedPhrase( - words24OptionSelected = false, - words = words.mapIndexed { index, s -> - MultiWalletSeedPhraseUM.GenerateSeedPhrase.MnemonicGridItem(index, s) - }.toImmutableList(), - onWords24OptionSwitch = { - _uiState.update { - if (it !is MultiWalletSeedPhraseUM.GenerateSeedPhrase) return@update it - it.copy(words24OptionSelected = it.words24OptionSelected.not()) - } - state.update { it.copy(words24Option = it.words24Option.not()) } + private fun getStartState(): MultiWalletSeedPhraseUM { + return MultiWalletSeedPhraseUM.Start( + onImportSeedPhraseClicked = {}, + onGenerateSeedPhraseClicked = { + generateSeedPhrase() + openGeneratedSeedPhrase() }, - onContinueClick = { + onLearnMoreClicked = { + urlOpener.openUrl(seedPhraseLearnMoreUrl()) }, ) } + + private fun openGeneratedSeedPhrase() { + // TODO analytics SeedPhraseEvents.ButtonGenerateSeedPhrase + + val words12 = state.value.generatedWords12 ?: return + val words24 = state.value.generatedWords24 ?: return + + // TODO disallow screenshots + + _uiState.value = generateSeedPhraseUiStateBuilder.getState( + generatedWords12 = words12, + generatedWords24 = words24, + ) + } + + private fun openWordsCheck() { + _uiState.value = seedPhraseCheckUiStateBuilder.getState() + } + + private fun generateSeedPhrase() { + val words12 = mnemonicRepository.generateMnemonic(MnemonicRepository.MnemonicType.Words12) + val words24 = mnemonicRepository.generateMnemonic(MnemonicRepository.MnemonicType.Words24) + + state.update { + it.copy( + generatedWords12 = words12, + generatedWords24 = words24, + ) + } + } + + private inline fun updateUiStateSpecific(block: (T) -> T) { + _uiState.update { + if (it !is T) return@update it + block(it) + } + } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt index 8b854a461c..796cd1599a 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt @@ -1,6 +1,10 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model +import com.tangem.crypto.bip39.Mnemonic + data class SeedPhraseState( - val generatedWords: List? = null, + val generatedWords12: Mnemonic? = null, + val generatedWords24: Mnemonic? = null, val words24Option: Boolean = false, + val readyToImport: Boolean = false, ) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/Utils.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/Utils.kt new file mode 100644 index 0000000000..af12fa1b39 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/Utils.kt @@ -0,0 +1,27 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model + +import android.net.Uri +import com.tangem.utils.SupportedLanguages +import java.util.Locale + +internal fun seedPhraseLearnMoreUrl(): String { + val language = Locale.getDefault().language + + val languageBy = "by" + if (SupportedLanguages.RUSSIAN.equals(language, true) || + languageBy.equals(language, true) + ) { + SupportedLanguages.RUSSIAN + } else { + SupportedLanguages.ENGLISH + } + + val webUri = Uri.Builder() + .scheme("https") + .authority("tangem.com") + .appendPath(language) + .appendPath("blog/post/seed-phrase-a-risky-solution") + .build() + + return webUri.toString() +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/GenerateSeedPhraseUiStateBuilder.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/GenerateSeedPhraseUiStateBuilder.kt new file mode 100644 index 0000000000..2d45732a3a --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/GenerateSeedPhraseUiStateBuilder.kt @@ -0,0 +1,52 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder + +import com.tangem.crypto.bip39.Mnemonic +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.SeedPhraseState +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update + +class GenerateSeedPhraseUiStateBuilder( + private val state: MutableStateFlow, + private val updateUiState: ( + (MultiWalletSeedPhraseUM.GenerateSeedPhrase) -> MultiWalletSeedPhraseUM.GenerateSeedPhrase, + ) -> Unit, + private val onContinue: () -> Unit, +) { + + fun getState(generatedWords12: Mnemonic, generatedWords24: Mnemonic): MultiWalletSeedPhraseUM.GenerateSeedPhrase { + val words12 = generatedWords12.mnemonicComponents.mapIndexed { index, s -> + MultiWalletSeedPhraseUM.GenerateSeedPhrase.MnemonicGridItem(index + 1, s) + }.toImmutableList() + val words24 = generatedWords24.mnemonicComponents.mapIndexed { index, s -> + MultiWalletSeedPhraseUM.GenerateSeedPhrase.MnemonicGridItem(index + 1, s) + }.toImmutableList() + + return MultiWalletSeedPhraseUM.GenerateSeedPhrase( + words24OptionSelected = state.value.words24Option, + words = words12, + onWords24OptionSwitch = { switchType(words12, words24) }, + onContinueClick = onContinue, + ) + } + + private fun switchType( + generatedWords12: ImmutableList, + generatedWords24: ImmutableList, + ) { + updateUiState { uiSt -> + state.update { it.copy(words24Option = uiSt.words24OptionSelected.not()) } + + uiSt.copy( + words24OptionSelected = uiSt.words24OptionSelected.not(), + words = if (uiSt.words24OptionSelected) { + generatedWords12 + } else { + generatedWords24 + }, + ) + } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/SeedPhraseCheckUiStateBuilder.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/SeedPhraseCheckUiStateBuilder.kt new file mode 100644 index 0000000000..eca9aa9570 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/SeedPhraseCheckUiStateBuilder.kt @@ -0,0 +1,105 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder + +import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.SeedPhraseState +import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update + +class SeedPhraseCheckUiStateBuilder( + private val state: MutableStateFlow, + private val currentUiState: () -> MultiWalletSeedPhraseUM, + private val updateUiState: ( + (MultiWalletSeedPhraseUM.GeneratedWordsCheck) -> MultiWalletSeedPhraseUM.GeneratedWordsCheck, + ) -> Unit, + private val importWallet: () -> Unit, +) { + + @Suppress("MagicNumber") + fun getState(): MultiWalletSeedPhraseUM.GeneratedWordsCheck { + val wordFields = List(3) { index -> + val shownIndex = when (index) { + 0 -> 2 + 1 -> 7 + 2 -> 11 + else -> error("") + } + + MultiWalletSeedPhraseUM.GeneratedWordsCheck.WordField( + index = shownIndex, + word = TextFieldValue(""), + error = false, + onChange = { changeField -> + updateUiState { st -> + val newState = st.copy( + wordFields = st.wordFields.map { wordField -> + if (wordField.index == shownIndex) { + wordField.updateField(changeField, shownIndex) + } else { + wordField + } + }.toImmutableList(), + ) + + val allFieldsCorrect = newState.allFieldsCorrect() + state.update { it.copy(readyToImport = allFieldsCorrect) } + + newState.copy( + createWalletButtonEnabled = allFieldsCorrect, + ) + } + }, + ) + }.toImmutableList() + + return MultiWalletSeedPhraseUM.GeneratedWordsCheck( + wordFields = wordFields, + createWalletButtonEnabled = false, + createWalletButtonProgress = false, + onCreateWalletButtonClick = { + val currentState = currentUiState() as? MultiWalletSeedPhraseUM.GeneratedWordsCheck + ?: return@GeneratedWordsCheck + if (currentState.createWalletButtonEnabled) { + importWallet() + } + }, + ) + } + + private fun MultiWalletSeedPhraseUM.GeneratedWordsCheck.WordField.updateField( + newText: TextFieldValue, + shownIndex: Int, + ): MultiWalletSeedPhraseUM.GeneratedWordsCheck.WordField { + val correct = checkWordField(word = newText.text, shownIndex = shownIndex) + + return copy( + word = newText, + error = correct.not(), + ) + } + + private fun MultiWalletSeedPhraseUM.GeneratedWordsCheck.allFieldsCorrect(): Boolean { + return wordFields.all { + checkWordField( + word = it.word.text, + shownIndex = it.index, + ) + } + } + + private fun checkWordField(word: String, shownIndex: Int): Boolean { + val currentState = state.value + + currentState.generatedWords12 ?: return false + currentState.generatedWords24 ?: return false + + val wordList = if (currentState.words24Option) { + currentState.generatedWords24 + } else { + currentState.generatedWords12 + }.mnemonicComponents + + return wordList[shownIndex - 1] == word + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt index bba9e89256..4e651c9944 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt @@ -23,6 +23,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.st import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM.GenerateSeedPhrase.MnemonicGridItem import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList @Composable fun MultiWalletSeedPhraseWords(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifier: Modifier = Modifier) { @@ -37,19 +38,18 @@ fun MultiWalletSeedPhraseWords(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase SeedPhraseGridBlock( mnemonicGridItems = state.words, modifier = Modifier - .padding(top = 20.dp, bottom = 92.dp), + .weight(1f) + .fillMaxWidth() + .padding(top = 20.dp, bottom = 32.dp), ) - Box(Modifier.weight(1f)) { - PrimaryButton( - modifier = Modifier - .align(Alignment.BottomCenter) - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), - text = stringResource(id = R.string.common_continue), - onClick = state.onContinueClick, - ) - } + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + text = stringResource(id = R.string.common_continue), + onClick = state.onContinueClick, + ) } } @@ -61,7 +61,7 @@ private fun SegmentSeedBlock(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, config = persistentListOf(0, 1), initialSelectedItem = if (state.words24OptionSelected.not()) 0 else 1, onClick = { - if (state.words24OptionSelected && it == 1 || state.words24OptionSelected.not() && it == 0) { + if (state.words24OptionSelected && it == 0 || state.words24OptionSelected.not() && it == 1) { state.onWords24OptionSwitch() } }, @@ -160,10 +160,10 @@ private inline fun VerticalGrid( ) { val columnLength = items.size / 2 Row( - modifier = modifier.fillMaxWidth(), + modifier = modifier, horizontalArrangement = Arrangement.SpaceEvenly, ) { - for (index in 0 until 2) { + repeat(2) { index -> Column { for (i in 0 until columnLength) { val item = items[index * columnLength + i] @@ -174,34 +174,18 @@ private inline fun VerticalGrid( } } -@Preview(showBackground = true) +@Preview(showBackground = true, heightDp = 640) @Composable private fun Preview() { TangemThemePreview { MultiWalletSeedPhraseWords( state = MultiWalletSeedPhraseUM.GenerateSeedPhrase( - words = persistentListOf( + words = List(24) { MnemonicGridItem( - index = 0, + index = it + 1, mnemonic = "word1", - ), - MnemonicGridItem( - index = 1, - mnemonic = "word1", - ), - MnemonicGridItem( - index = 2, - mnemonic = "word1", - ), - MnemonicGridItem( - index = 3, - mnemonic = "word1", - ), - MnemonicGridItem( - index = 4, - mnemonic = "word1", - ), - ), + ) + }.toImmutableList(), ), ) } 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 5a8c2f5fca..2cb99f6ed2 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 @@ -42,6 +42,7 @@ internal class OnboardingMultiWalletModel @Inject constructor( private fun getInitialStep(): OnboardingMultiWalletState.Step { val card = params.scanResponse.card + // todo check local storage for scan response return when { card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> OnboardingMultiWalletState.Step.AddBackupDevice From 0a78db08ecfbf07efa7880189cdc6f88835848f3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Nov 2024 20:28:56 +0700 Subject: [PATCH 44/99] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- gradle/dependencies.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 17f16c2e69..cce49feacb 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 17f16c2e698238e2b79c2360d4f80b2caa8583a8 +Subproject commit cce49feacb18dc9ad7cc5da62d58f1b3338a1057 diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index a988ee202a..7e0c09dcdd 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-863" +tangemBlockchainSdk = "develop-868" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-403" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From db358d8014bc9551841f721b8e121b9cb3f4f571 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Nov 2024 13:41:23 +0500 Subject: [PATCH 45/99] Updated on 2026-08-14 --- .../tap/di/domain/OnrampDomainModule.kt | 10 +++ .../models/response/OnrampStatusResponse.kt | 42 ++++++++-- .../local/preferences/PreferencesKeys.kt | 2 + .../utils/AppPreferencesStoreExt.kt | 8 ++ .../data/onramp/DefaultOnrampErrorResolver.kt | 6 +- .../data/onramp/DefaultOnrampRepository.kt | 9 +++ .../DefaultOnrampTransactionRepository.kt | 77 +++++++++++++++++++ .../data/onramp/converters/StatusConverter.kt | 30 ++++++++ .../tangem/data/onramp/di/OnrampDataModule.kt | 22 ++++++ domain/onramp/build.gradle.kts | 2 + domain/onramp/models/build.gradle.kts | 2 + .../domain/onramp/model/OnrampStatus.kt | 38 +++++++++ .../onramp/model/cache/OnrampTransaction.kt | 20 +++++ .../domain/onramp/GetOnrampStatusUseCase.kt | 21 +++++ .../onramp/repositories/OnrampRepository.kt | 5 ++ .../OnrampTransactionRepository.kt | 17 ++++ 16 files changed, 303 insertions(+), 8 deletions(-) create mode 100644 data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt create mode 100644 data/onramp/src/main/java/com/tangem/data/onramp/converters/StatusConverter.kt create mode 100644 domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt create mode 100644 domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.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 81e25125ae..1b04e8f513 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,6 +1,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.onramp.* +import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampRepository import dagger.Module import dagger.Provides @@ -48,6 +49,15 @@ internal object OnrampDomainModule { return CheckOnrampAvailabilityUseCase(onrampRepository) } + @Provides + @Singleton + fun provideGetOnrampStatusUseCase( + onrampRepository: OnrampRepository, + onrampErrorResolver: OnrampErrorResolver, + ): GetOnrampStatusUseCase { + return GetOnrampStatusUseCase(onrampRepository, onrampErrorResolver) + } + @Provides @Singleton fun provideGetOnrampCurrencyUseCase(onrampRepository: OnrampRepository): GetOnrampCurrencyUseCase { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt index 6ef0416464..301fd2421f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt @@ -14,8 +14,8 @@ data class OnrampStatusResponse( @Json(name = "payoutAddress") val payoutAddress: String, - // @Json(name = "status") - // val status: ??? + @Json(name = "status") + val status: Status, @Json(name = "failReason") val failReason: String?, @@ -48,14 +48,46 @@ data class OnrampStatusResponse( val toDecimals: String, @Json(name = "toAmount") - val toAmount: String, + val toAmount: String?, @Json(name = "toActualAmount") - val toActualAmount: String, + val toActualAmount: String?, @Json(name = "paymentMethod") val paymentMethod: String, @Json(name = "countryCode") val countryCode: String, -) \ No newline at end of file +) + +enum class Status { + @Json(name = "created") + Created, + + @Json(name = "expired") + Expired, + + @Json(name = "waiting-for-payment") + WaitingForPayment, + + @Json(name = "payment-processing") + PaymentProcessing, + + @Json(name = "verifying") + Verifying, + + @Json(name = "failed") + Failed, + + @Json(name = "paid") + Paid, + + @Json(name = "sending") + Sending, + + @Json(name = "finished") + Finished, + + @Json(name = "paused") + Paused, +} \ 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 a049a7db22..9f4cf80fea 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 @@ -107,6 +107,8 @@ object PreferencesKeys { val ONRAMP_DEFAULT_COUNTRY by lazy { stringPreferencesKey(name = "onrampDefaultCountry") } + val ONRAMP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "onrampTransactionsStatuses") } + // region Permission fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission") diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt index a7fd3cafef..0369cf22cd 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt @@ -150,4 +150,12 @@ suspend inline fun AppPreferencesStore.getObjectSetSync(key: Prefere ?.get(key) ?.let(adapter::fromJson) .orEmpty() +} + +/** Get flow of set of [T] by string [key], or empty if data is not found */ +inline fun AppPreferencesStore.getObjectSet(key: Preferences.Key): Flow> { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) + return data.map { + it[key]?.let(adapter::fromJson) ?: emptySet() + } } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt index 0ec508bd89..b03b5c6c69 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt @@ -5,9 +5,9 @@ import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.domain.onramp.model.OnrampError import com.tangem.domain.onramp.repositories.OnrampErrorResolver -internal class DefaultOnrampErrorResolver( - private val onrampErrorConverter: OnrampErrorConverter, -) : OnrampErrorResolver { +internal class DefaultOnrampErrorResolver : OnrampErrorResolver { + + private val onrampErrorConverter = OnrampErrorConverter() override fun resolve(throwable: Throwable): OnrampError { return if (throwable is ApiResponseError.HttpException) { 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 31afbf4c67..ef6cd4523e 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 @@ -2,6 +2,7 @@ package com.tangem.data.onramp import com.tangem.data.onramp.converters.CountryConverter import com.tangem.data.onramp.converters.CurrencyConverter +import com.tangem.data.onramp.converters.StatusConverter 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 @@ -13,6 +14,7 @@ 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.model.OnrampStatus import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow @@ -27,6 +29,7 @@ internal class DefaultOnrampRepository( private val currencyConverter = CurrencyConverter() private val countryConverter = CountryConverter(currencyConverter) + private val statusConverter = StatusConverter() override suspend fun getCurrencies(): List = withContext(dispatchers.io) { onrampApi.getCurrencies() @@ -46,6 +49,12 @@ internal class DefaultOnrampRepository( .let(countryConverter::convert) } + override suspend fun getStatus(txId: String): OnrampStatus = withContext(dispatchers.io) { + onrampApi.getStatus(txId) + .getOrThrow() + .let(statusConverter::convert) + } + override suspend fun saveDefaultCurrency(currency: OnrampCurrency) = withContext(dispatchers.io) { appPreferencesStore.storeObject( key = PreferencesKeys.ONRAMP_DEFAULT_CURRENCY, diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt new file mode 100644 index 0000000000..85fb0b94f9 --- /dev/null +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt @@ -0,0 +1,77 @@ +package com.tangem.data.onramp + +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectSet +import com.tangem.datasource.local.preferences.utils.getObjectSetSync +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.extensions.addOrReplace +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +internal class DefaultOnrampTransactionRepository( + private val appPreferencesStore: AppPreferencesStore, + private val dispatchers: CoroutineDispatcherProvider, +) : OnrampTransactionRepository { + + override suspend fun storeTransaction(transaction: OnrampTransaction) { + withContext(dispatchers.io) { + appPreferencesStore.editData { mutablePreferences -> + val stored = mutablePreferences.getObjectSet( + PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + ) + val updated = stored?.toMutableSet() + ?.addOrReplace(transaction) { it.txId == transaction.txId } + ?: mutableSetOf(transaction) + + mutablePreferences.setObjectSet( + key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + value = updated, + ) + } + } + } + + override suspend fun getTransactionById(txId: String): OnrampTransaction? = withContext(dispatchers.io) { + val stored = appPreferencesStore.getObjectSetSync( + PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + ) + + stored.firstOrNull { it.txId == txId } + } + + override fun getTransactions( + userWalletId: UserWalletId, + toCryptoCurrency: CryptoCurrency.ID, + ): Flow> = appPreferencesStore + .getObjectSet(PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY) + .map { transactions -> + transactions.filter { + it.userWalletId == userWalletId && it.toCurrency.id == toCryptoCurrency + } + } + + override suspend fun removeTransaction(txId: String) { + withContext(dispatchers.io) { + appPreferencesStore.editData { mutablePreferences -> + runCatching { + val stored = mutablePreferences.getObjectSet( + PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + )?.toMutableSet() + + stored?.removeIf { it.txId == txId } + + mutablePreferences.setObjectSet( + key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + value = stored ?: emptySet(), + ) + } + } + } + } +} \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/StatusConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/StatusConverter.kt new file mode 100644 index 0000000000..70805a77fe --- /dev/null +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/StatusConverter.kt @@ -0,0 +1,30 @@ +package com.tangem.data.onramp.converters + +import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.utils.converter.Converter + +internal class StatusConverter : Converter { + override fun convert(value: OnrampStatusResponse): OnrampStatus { + return OnrampStatus( + txId = value.txId, + providerId = value.providerId, + payoutAddress = value.payoutAddress, + status = OnrampStatus.Status.valueOf(value.status.name), + failReason = value.failReason, + externalTxId = value.externalTxId, + externalTxUrl = value.externalTxUrl, + payoutHash = value.payoutHash, + createdAt = value.createdAt, + fromCurrencyCode = value.fromCurrencyCode, + fromAmount = value.fromAmount, + toContractAddress = value.toContractAddress, + toNetwork = value.toNetwork, + toDecimals = value.toDecimals, + toAmount = value.toAmount, + toActualAmount = value.toActualAmount, + paymentMethod = value.paymentMethod, + countryCode = value.countryCode, + ) + } +} \ 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 d47543851a..fed11e54d0 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 @@ -1,9 +1,13 @@ package com.tangem.data.onramp.di +import com.tangem.data.onramp.DefaultOnrampErrorResolver import com.tangem.data.onramp.DefaultOnrampRepository +import com.tangem.data.onramp.DefaultOnrampTransactionRepository import com.tangem.datasource.api.onramp.OnrampApi import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampRepository +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -28,4 +32,22 @@ internal object OnrampDataModule { appPreferencesStore = appPreferencesStore, ) } + + @Provides + @Singleton + fun provideOnrampTransactionRepository( + appPreferencesStore: AppPreferencesStore, + dispatchers: CoroutineDispatcherProvider, + ): OnrampTransactionRepository { + return DefaultOnrampTransactionRepository( + appPreferencesStore = appPreferencesStore, + dispatchers = dispatchers, + ) + } + + @Provides + @Singleton + fun provideOnrampErrorResolver(): OnrampErrorResolver { + return DefaultOnrampErrorResolver() + } } \ No newline at end of file diff --git a/domain/onramp/build.gradle.kts b/domain/onramp/build.gradle.kts index 43956b9497..b676e92120 100644 --- a/domain/onramp/build.gradle.kts +++ b/domain/onramp/build.gradle.kts @@ -6,6 +6,8 @@ plugins { dependencies { api(projects.domain.onramp.models) + api(projects.domain.tokens.models) + api(projects.domain.wallets.models) api(projects.domain.core) implementation(deps.kotlin.serialization) diff --git a/domain/onramp/models/build.gradle.kts b/domain/onramp/models/build.gradle.kts index 7558c5ca61..dc13a99957 100644 --- a/domain/onramp/models/build.gradle.kts +++ b/domain/onramp/models/build.gradle.kts @@ -6,6 +6,8 @@ plugins { dependencies { implementation(projects.domain.core) + implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets.models) implementation(deps.kotlin.serialization) implementation(deps.jodatime) diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt new file mode 100644 index 0000000000..eec20f05f7 --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt @@ -0,0 +1,38 @@ +package com.tangem.domain.onramp.model + +import kotlinx.serialization.Serializable + +@Serializable +data class OnrampStatus( + val txId: String, + val providerId: String, + val payoutAddress: String, + val status: Status, + val failReason: String?, + val externalTxId: String, + val externalTxUrl: String?, + val payoutHash: String?, + val createdAt: String, + val fromCurrencyCode: String, + val fromAmount: String, + val toContractAddress: String, + val toNetwork: String, + val toDecimals: String, + val toAmount: String?, + val toActualAmount: String?, + val paymentMethod: String, + val countryCode: String, +) { + enum class Status { + Created, + Expired, + Paused, + WaitingForPayment, + PaymentProcessing, + Verifying, + Failed, + Paid, + Sending, + Finished, + } +} \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt new file mode 100644 index 0000000000..9753bc85e7 --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt @@ -0,0 +1,20 @@ +package com.tangem.domain.onramp.model.cache + +import com.tangem.domain.core.serialization.SerializedBigDecimal +import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.serialization.Serializable + +@Serializable +data class OnrampTransaction( + val txId: String, + val userWalletId: UserWalletId, + val fromAmount: SerializedBigDecimal, + val fromCurrency: OnrampCurrency, + val toAmount: SerializedBigDecimal, + val toCurrency: CryptoCurrency, + val timestamp: Long, + val providerName: String, + val providerImageUrl: String, +) \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt new file mode 100644 index 0000000000..4692c6d6b7 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampError +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.repositories.OnrampErrorResolver +import com.tangem.domain.onramp.repositories.OnrampRepository + +class GetOnrampStatusUseCase( + private val onrampRepository: OnrampRepository, + private val errorResolver: OnrampErrorResolver, +) { + + suspend operator fun invoke(txId: String): Either { + return Either.catch { + onrampRepository.getStatus(txId) + }.mapLeft { + errorResolver.resolve(it) + } + } +} \ 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 85b4e27f69..7e6dc7df12 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,12 +2,17 @@ package com.tangem.domain.onramp.repositories import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.domain.onramp.model.OnrampStatus import kotlinx.coroutines.flow.Flow interface OnrampRepository { + // api suspend fun getCurrencies(): List suspend fun getCountries(): List suspend fun getCountryByIp(): OnrampCountry + suspend fun getStatus(txId: String): OnrampStatus + + // cache suspend fun saveDefaultCurrency(currency: OnrampCurrency) suspend fun getDefaultCurrencySync(): OnrampCurrency? fun getDefaultCurrency(): Flow diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt new file mode 100644 index 0000000000..5796bec18e --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.onramp.repositories + +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +interface OnrampTransactionRepository { + + suspend fun storeTransaction(transaction: OnrampTransaction) + + suspend fun getTransactionById(txId: String): OnrampTransaction? + + fun getTransactions(userWalletId: UserWalletId, toCryptoCurrency: CryptoCurrency.ID): Flow> + + suspend fun removeTransaction(txId: String) +} \ No newline at end of file From f1eb31f5f4c4a70873a3907054d90ee01f968f23 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 15 Nov 2024 11:26:02 +0500 Subject: [PATCH 46/99] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 13 ++++--- .../com/tangem/common/routing/AppRoute.kt | 7 ++++ .../component/OnrampSuccessComponent.kt | 11 ++++++ .../success/DefaultOnrampSuccessComponent.kt | 36 +++++++++++++++++++ .../di/OnrampSuccessComponentModelModule.kt | 19 ++++++++++ .../di/OnrampSuccessComponentModule.kt | 19 ++++++++++ .../entity/OnrampSuccessComponentUM.kt | 16 +++++++++ .../model/OnrampSuccessComponentModel.kt | 25 +++++++++++++ 8 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt 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 48042171a4..46a9c70161 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 @@ -12,10 +12,7 @@ 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.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.onramp.component.* import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter @@ -52,6 +49,7 @@ internal class ChildFactory @Inject constructor( private val manageTokensComponentFactory: ManageTokensComponent.Factory, private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory, private val onrampComponentFactory: OnrampComponent.Factory, + private val onrampSuccessComponentFactory: OnrampSuccessComponent.Factory, private val buyCryptoComponentFactory: BuyCryptoComponent.Factory, private val sellCryptoComponentFactory: SellCryptoComponent.Factory, private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory, @@ -203,6 +201,13 @@ internal class ChildFactory @Inject constructor( componentFactory = onrampComponentFactory, ) } + is AppRoute.OnrampSuccess -> { + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = OnrampSuccessComponent.Params(route.txId), + componentFactory = onrampSuccessComponentFactory, + ) + } is AppRoute.BuyCrypto -> { 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 66993b210a..da4f005d32 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,13 @@ sealed class AppRoute(val path: String) : Route { override fun getBundle(): Bundle = bundle(serializer()) } + @Serializable + data class OnrampSuccess( + val txId: String, + ) : AppRoute(path = "/onramp/success/$txId"), RouteBundleParams { + override fun getBundle(): Bundle = bundle(serializer()) + } + @Serializable data class BuyCrypto( val userWalletId: UserWalletId, diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt new file mode 100644 index 0000000000..70b6e543c4 --- /dev/null +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt @@ -0,0 +1,11 @@ +package com.tangem.features.onramp.component + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface OnrampSuccessComponent : ComposableContentComponent { + + data class Params(val txId: String?) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt new file mode 100644 index 0000000000..0f5787d3b3 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt @@ -0,0 +1,36 @@ +package com.tangem.features.onramp.success + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.features.onramp.component.OnrampSuccessComponent +import com.tangem.features.onramp.success.model.OnrampSuccessComponentModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnrampSuccessComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: OnrampSuccessComponent.Params, +) : OnrampSuccessComponent, AppComponentContext by appComponentContext { + + private val model: OnrampSuccessComponentModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsState() + + // todo onramp [REDACTED_JIRA] + } + + @AssistedFactory + interface Factory : OnrampSuccessComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnrampSuccessComponent.Params, + ): DefaultOnrampSuccessComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt new file mode 100644 index 0000000000..ba6f6664fd --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt @@ -0,0 +1,19 @@ +package com.tangem.features.onramp.success.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.success.model.OnrampSuccessComponentModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampSuccessComponentModelModule { + @Binds + @IntoMap + @ClassKey(OnrampSuccessComponentModel::class) + fun bindOnrampSuccessComponentModel(model: OnrampSuccessComponentModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt new file mode 100644 index 0000000000..fe2545877b --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt @@ -0,0 +1,19 @@ +package com.tangem.features.onramp.success.di + +import com.tangem.features.onramp.component.OnrampSuccessComponent +import com.tangem.features.onramp.success.DefaultOnrampSuccessComponent +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 OnrampSuccessComponentModule { + @Binds + @Singleton + fun bindOnrampSuccessComponentFactory( + factory: DefaultOnrampSuccessComponent.Factory, + ): OnrampSuccessComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt new file mode 100644 index 0000000000..3f0bea7160 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt @@ -0,0 +1,16 @@ +package com.tangem.features.onramp.success.entity + +sealed class OnrampSuccessComponentUM { + + data object Loading : OnrampSuccessComponentUM() + + data class Content( + val txId: String, + val createdAt: Long, + val providerId: String, + val currencyImageUrl: String, + val fromAmount: String, + val toAmount: String, + val onBackClick: () -> Unit, + ) : OnrampSuccessComponentUM() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt new file mode 100644 index 0000000000..bdda90effd --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt @@ -0,0 +1,25 @@ +package com.tangem.features.onramp.success.model + +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.onramp.component.OnrampSuccessComponent +import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject + +internal class OnrampSuccessComponentModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, +) : Model() { + + // todo onramp remove in [REDACTED_JIRA] + @Suppress("UnusedPrivateMember") + private val params: OnrampSuccessComponent.Params = paramsContainer.require() + private val _state: MutableStateFlow = MutableStateFlow( + value = OnrampSuccessComponentUM.Loading, + ) + val state: StateFlow get() = _state.asStateFlow() +} \ No newline at end of file From da6ee825abc3759a741c9ed4952c6067b162cbd0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Nov 2024 18:04:57 +0500 Subject: [PATCH 47/99] Updated on 2026-08-14 --- .../tap/di/domain/OnrampDomainModule.kt | 33 ++++++++++++++++ .../ResponseCryptoCurrenciesFactory.kt | 8 +--- .../DefaultOnrampTransactionRepository.kt | 4 +- .../repository/DefaultCurrenciesRepository.kt | 35 +++++++++-------- .../domain/onramp/model/OnrampStatus.kt | 38 +++++++++++++------ .../onramp/model/cache/OnrampTransaction.kt | 4 +- .../onramp/GetOnrampTransactionUseCase.kt | 21 ++++++++++ .../onramp/GetOnrampTransactionsUseCase.kt | 28 ++++++++++++++ .../onramp/OnrampRemoveTransactionUseCase.kt | 18 +++++++++ .../onramp/OnrampSaveTransactionUseCase.kt | 16 ++++++++ .../OnrampTransactionRepository.kt | 2 +- .../domain/tokens/GetCryptoCurrencyUseCase.kt | 28 ++++++++++++++ .../tokens/repository/CurrenciesRepository.kt | 11 ++++++ 13 files changed, 208 insertions(+), 38 deletions(-) create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionUseCase.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionsUseCase.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampRemoveTransactionUseCase.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveTransactionUseCase.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 1b04e8f513..2c276a7117 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 @@ -3,6 +3,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.onramp.* import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampRepository +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -63,4 +64,36 @@ internal object OnrampDomainModule { fun provideGetOnrampCurrencyUseCase(onrampRepository: OnrampRepository): GetOnrampCurrencyUseCase { return GetOnrampCurrencyUseCase(onrampRepository) } + + @Provides + @Singleton + fun provideGetOnrampTransactionsUseCase( + onrampTransactionRepository: OnrampTransactionRepository, + ): GetOnrampTransactionsUseCase { + return GetOnrampTransactionsUseCase(onrampTransactionRepository) + } + + @Provides + @Singleton + fun provideGetOnrampTransactionUseCase( + onrampTransactionRepository: OnrampTransactionRepository, + ): GetOnrampTransactionUseCase { + return GetOnrampTransactionUseCase(onrampTransactionRepository) + } + + @Provides + @Singleton + fun provideOnrampRemoveTransactionUseCase( + onrampTransactionRepository: OnrampTransactionRepository, + ): OnrampRemoveTransactionUseCase { + return OnrampRemoveTransactionUseCase(onrampTransactionRepository) + } + + @Provides + @Singleton + fun provideOnrampSaveTransactionUseCase( + onrampTransactionRepository: OnrampTransactionRepository, + ): OnrampSaveTransactionUseCase { + return OnrampSaveTransactionUseCase(onrampTransactionRepository) + } } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt index fb9ff22adb..047150c42e 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt @@ -14,15 +14,11 @@ import com.tangem.blockchain.common.Token as SdkToken class ResponseCryptoCurrenciesFactory { - fun createCurrency( - currencyId: CryptoCurrency.ID, - response: UserTokensResponse, - scanResponse: ScanResponse, - ): CryptoCurrency { + fun createCurrency(currencyId: String, response: UserTokensResponse, scanResponse: ScanResponse): CryptoCurrency { return response.tokens .asSequence() .mapNotNull { createCurrency(it, scanResponse) } - .first { it.id == currencyId } + .first { it.id.value == currencyId } } fun createCurrencies(response: UserTokensResponse, scanResponse: ScanResponse): List { diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt index 85fb0b94f9..eaaad04a78 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt @@ -47,12 +47,12 @@ internal class DefaultOnrampTransactionRepository( override fun getTransactions( userWalletId: UserWalletId, - toCryptoCurrency: CryptoCurrency.ID, + cryptoCurrencyId: CryptoCurrency.ID, ): Flow> = appPreferencesStore .getObjectSet(PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY) .map { transactions -> transactions.filter { - it.userWalletId == userWalletId && it.toCurrency.id == toCryptoCurrency + it.userWalletId == userWalletId && it.toCurrencyId == cryptoCurrencyId.value } } 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 31282f6f34..1d25e1ea81 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 @@ -293,23 +293,28 @@ internal class DefaultCurrenciesRepository( userWalletId: UserWalletId, id: CryptoCurrency.ID, ): CryptoCurrency = withContext(dispatchers.io) { - val userWallet = getUserWallet(userWalletId) - ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) - - val response = requireNotNull( - value = getSavedUserTokensResponseSync(key = userWalletId), - lazyMessage = { - "Unable to find tokens response for user wallet with provided ID: $userWalletId" - }, - ) - - responseCurrenciesFactory.createCurrency( - currencyId = id, - response = response, - scanResponse = userWallet.scanResponse, - ) + getMultiCurrencyWalletCurrency(userWalletId, id.value) } + override suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: String): CryptoCurrency = + withContext(dispatchers.io) { + val userWallet = getUserWallet(userWalletId) + ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) + + val response = requireNotNull( + value = getSavedUserTokensResponseSync(key = userWalletId), + lazyMessage = { + "Unable to find tokens response for user wallet with provided ID: $userWalletId" + }, + ) + + responseCurrenciesFactory.createCurrency( + currencyId = id, + response = response, + scanResponse = userWallet.scanResponse, + ) + } + override suspend fun getNetworkCoin( userWalletId: UserWalletId, networkId: Network.ID, diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt index eec20f05f7..69ecefb695 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt @@ -23,16 +23,32 @@ data class OnrampStatus( val paymentMethod: String, val countryCode: String, ) { - enum class Status { - Created, - Expired, - Paused, - WaitingForPayment, - PaymentProcessing, - Verifying, - Failed, - Paid, - Sending, - Finished, + enum class Status(val order: Int) { + Created(order = 1), + Expired(order = 2), + Paused(order = 3), + WaitingForPayment(order = 4), + PaymentProcessing(order = 5), + Verifying(order = 6), + Failed(order = 7), + Paid(order = 8), + Sending(order = 9), + Finished(order = 10), + ; + + fun isTerminal(): Boolean = when (this) { + Expired, + Failed, + Paused, + Finished, + -> true + Created, + WaitingForPayment, + PaymentProcessing, + Verifying, + Paid, + Sending, + -> false + } } } \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt index 9753bc85e7..96f069b02f 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt @@ -2,7 +2,6 @@ package com.tangem.domain.onramp.model.cache import com.tangem.domain.core.serialization.SerializedBigDecimal import com.tangem.domain.onramp.model.OnrampCurrency -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId import kotlinx.serialization.Serializable @@ -13,8 +12,7 @@ data class OnrampTransaction( val fromAmount: SerializedBigDecimal, val fromCurrency: OnrampCurrency, val toAmount: SerializedBigDecimal, - val toCurrency: CryptoCurrency, - val timestamp: Long, + val toCurrencyId: String, val providerName: String, val providerImageUrl: String, ) \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionUseCase.kt new file mode 100644 index 0000000000..0e33b2e91a --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionUseCase.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampError +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository + +class GetOnrampTransactionUseCase( + private val onrampTransactionRepository: OnrampTransactionRepository, +) { + + suspend operator fun invoke(txId: String): Either { + return Either.catch { + requireNotNull( + onrampTransactionRepository.getTransactionById(txId), + ) + }.mapLeft { + OnrampError.UnknownError + } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionsUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionsUseCase.kt new file mode 100644 index 0000000000..af51320013 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionsUseCase.kt @@ -0,0 +1,28 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampError +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +class GetOnrampTransactionsUseCase( + private val onrampTransactionRepository: OnrampTransactionRepository, +) { + + operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ): Either>> { + return Either.catch { + onrampTransactionRepository.getTransactions( + userWalletId = userWalletId, + cryptoCurrencyId = cryptoCurrencyId, + ) + }.mapLeft { + OnrampError.UnknownError + } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampRemoveTransactionUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampRemoveTransactionUseCase.kt new file mode 100644 index 0000000000..4a6062ece8 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampRemoveTransactionUseCase.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampError +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository + +class OnrampRemoveTransactionUseCase( + private val onrampTransactionRepository: OnrampTransactionRepository, +) { + + suspend operator fun invoke(txId: String): Either { + return Either.catch { + onrampTransactionRepository.removeTransaction(txId) + }.mapLeft { + OnrampError.UnknownError + } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveTransactionUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveTransactionUseCase.kt new file mode 100644 index 0000000000..d939933d9c --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveTransactionUseCase.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampError +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository + +class OnrampSaveTransactionUseCase( + private val onrampTransactionRepository: OnrampTransactionRepository, +) { + + suspend operator fun invoke(transaction: OnrampTransaction): Either { + return Either.catch { onrampTransactionRepository.storeTransaction(transaction) } + .mapLeft { OnrampError.UnknownError } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt index 5796bec18e..24105314b1 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt @@ -11,7 +11,7 @@ interface OnrampTransactionRepository { suspend fun getTransactionById(txId: String): OnrampTransaction? - fun getTransactions(userWalletId: UserWalletId, toCryptoCurrency: CryptoCurrency.ID): Flow> + fun getTransactions(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> suspend fun removeTransaction(txId: String) } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyUseCase.kt index d0e47c2b62..b24eb525ae 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyUseCase.kt @@ -27,6 +27,22 @@ class GetCryptoCurrencyUseCase( return either { getCurrency(userWalletId, id) } } + /** + * Returns specific cryptocurrency for a given user wallet. + * + * !!! Important Use only [CryptoCurrency.ID.value] as cryptoCurrencyId + * + * @param userWalletId The ID of the user's wallet. + * @param cryptoCurrencyId String representation of the [CryptoCurrency.ID.value] of the cryptocurrency. + * @return An [Either] representing success (Right) or an error (Left) in fetching the status. + */ + suspend operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrencyId: String, + ): Either { + return either { getCurrency(userWalletId, cryptoCurrencyId) } + } + /** * Returns the primary cryptocurrency for a given user wallet. * @@ -49,6 +65,18 @@ class GetCryptoCurrencyUseCase( ) } + private suspend fun Raise.getCurrency( + userWalletId: UserWalletId, + id: String, + ): CryptoCurrency { + return catch( + block = { + currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, id) + }, + catch = { raise(CurrencyStatusError.DataError(it)) }, + ) + } + private suspend fun Raise.getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency { return catch( block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) }, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index bda0e8fdbd..27691a8495 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -167,6 +167,17 @@ interface CurrenciesRepository { */ suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: CryptoCurrency.ID): CryptoCurrency + /** + * Retrieves the cryptocurrency for a specific multi-currency user wallet. + * + * @param userWalletId The unique identifier of the user wallet. + * @param id The unique identifier of the cryptocurrency to be retrieved. + * @return The cryptocurrency associated with the user wallet and ID. + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * ID provided. + */ + suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: String): CryptoCurrency + /** * Get the coin for a specific network. * From 2f5d6525e22a7d8abe8ff45c6b3a2485f46b24dc Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Nov 2024 18:43:36 +0500 Subject: [PATCH 48/99] Updated on 2026-08-14 --- .../GiveTxPermisssionBottomSheet.kt | 9 +- .../ui/expressStatus/ExpressStatusBlock.kt | 228 ++++++++++++++++++ .../ExpressStatusNotificationBlock.kt | 32 +++ .../ui/expressStatus/state/ExpressStatusUM.kt | 54 +++++ .../notifications/ExpressNotificationsUM.kt | 28 +++ .../components/containers/FooterContainer.kt | 8 +- .../impl/presentation/ui/fee/SendCustomFee.kt | 3 +- .../ui/recipient/SendRecipientContent.kt | 10 +- .../ui/recipient/TextFieldWithPaste.kt | 2 +- .../components/ExchangeStatusNotifications.kt | 2 + .../exchange/ExchangeStatusBlock.kt | 1 + 11 files changed, 362 insertions(+), 15 deletions(-) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBlock.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusNotificationBlock.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/notifications/ExpressNotificationsUM.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt index 5d8b37fd46..4f4e74d559 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt @@ -31,10 +31,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.containers.FooterContainer import com.tangem.core.ui.components.inputrow.InputRowDefault -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.wrappedList +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import kotlinx.collections.immutable.ImmutableList @@ -118,7 +115,7 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh @Composable private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest) { FooterContainer( - footer = stringResource(id = R.string.give_permission_policy_type_footer), + footer = resourceReference(R.string.give_permission_policy_type_footer), modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), ) { AmountItem( @@ -130,7 +127,7 @@ private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest) } SpacerH16() FooterContainer( - footer = data.footerText.resolveReference(), + footer = data.footerText, modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), ) { FeeItem(fee = data.fee) diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBlock.kt new file mode 100644 index 0000000000..cdf3f1f8fb --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBlock.kt @@ -0,0 +1,228 @@ +package com.tangem.common.ui.expressStatus + +import androidx.annotation.DrawableRes +import androidx.compose.animation.* +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.common.ui.R +import com.tangem.common.ui.expressStatus.state.ExpressLinkUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusUM +import com.tangem.core.ui.components.SpacerWMax +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 kotlinx.collections.immutable.persistentListOf + +/** + * Block with express statuses + * + * @param state ui holder + * @param modifier modifier + * @see [Figma](https://www.figma.com/design/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?node-id=18459-26521&t=4jox7bfqUiXnm2h1-4) + */ +@Composable +fun ExpressStatusBlock(state: ExpressStatusUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action) + .padding( + vertical = TangemTheme.dimens.spacing14, + horizontal = TangemTheme.dimens.spacing12, + ), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing16), + ) { + Text( + text = state.title.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + SpacerWMax() + AnimatedVisibility(visible = state.link is ExpressLinkUM.Content) { + val link = remember(this) { state.link as ExpressLinkUM.Content } + Row( + modifier = Modifier.clickable { link.onClick() }, + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(id = link.icon), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + modifier = Modifier + .size(TangemTheme.dimens.spacing16) + .padding(end = TangemTheme.dimens.spacing2), + ) + Text( + text = link.text.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + ) + } + } + } + + Column { + state.statuses.forEachIndexed { index, item -> + ExpressStatusStep(item, index == state.statuses.lastIndex) + } + } + } +} + +@Composable +private fun ExpressStatusStep(status: ExpressStatusItemUM, isLast: Boolean) { + AnimatedContent( + targetState = status, + label = "Exchange Step Change Success", + transitionSpec = { + fadeIn(tween(durationMillis = 220)) togetherWith + fadeOut(tween(durationMillis = 220)) + }, + ) { content -> + Row { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + when (content.state) { + ExpressStatusItemState.Active -> StepInProgress() + ExpressStatusItemState.Default -> StepDefault() + ExpressStatusItemState.Done -> Step( + iconRes = R.drawable.ic_check_24, + iconColor = TangemTheme.colors.icon.primary1, + borderColor = TangemTheme.colors.field.focused, + ) + ExpressStatusItemState.Error -> Step( + iconRes = R.drawable.ic_close_24, + iconColor = TangemTheme.colors.icon.warning, + ) + ExpressStatusItemState.Warning -> Step( + iconRes = R.drawable.ic_close_24, + iconColor = TangemTheme.colors.icon.attention, + ) + } + if (!isLast) { + StepSeparator() + } + } + val textColor = when (status.state) { + ExpressStatusItemState.Active -> TangemTheme.colors.text.primary1 + ExpressStatusItemState.Default -> TangemTheme.colors.text.disabled + ExpressStatusItemState.Done -> TangemTheme.colors.text.primary1 + ExpressStatusItemState.Error -> TangemTheme.colors.text.warning + ExpressStatusItemState.Warning -> TangemTheme.colors.text.attention + } + Text( + text = content.text.resolveReference(), + style = TangemTheme.typography.body2, + color = textColor, + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing12), + ) + } + } +} + +@Composable +private fun StepDefault() { + Box( + modifier = Modifier + .size(TangemTheme.dimens.size20) + .border( + width = TangemTheme.dimens.size1_5, + color = TangemTheme.colors.field.focused, + shape = CircleShape, + ) + .padding(TangemTheme.dimens.spacing2), + ) +} + +@Composable +private fun Step(iconColor: Color, @DrawableRes iconRes: Int, borderColor: Color = iconColor) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = null, + tint = iconColor, + modifier = Modifier + .size(TangemTheme.dimens.size20) + .border( + width = TangemTheme.dimens.size1_5, + color = borderColor, + shape = CircleShape, + ) + .padding(TangemTheme.dimens.spacing2), + ) +} + +@Composable +private fun StepInProgress() { + CircularProgressIndicator( + color = TangemTheme.colors.icon.primary1, + strokeWidth = TangemTheme.dimens.size2, + modifier = Modifier + .padding(TangemTheme.dimens.spacing2) + .size(TangemTheme.dimens.size14), + ) +} + +@Composable +private fun StepSeparator() { + Box( + modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing2) + .size( + width = TangemTheme.dimens.size1_5, + height = TangemTheme.dimens.size10, + ) + .background( + color = TangemTheme.colors.field.focused, + shape = CircleShape, + ), + ) +} + +@Preview +@Composable +private fun Preview_ExchangeStatusBlock() { + val state = ExpressStatusUM( + title = resourceReference(R.string.express_exchange_status_title), + link = ExpressLinkUM.Content( + icon = R.drawable.ic_alert_24, + text = resourceReference(R.string.common_go_to_provider), + onClick = {}, + ), + statuses = persistentListOf( + ExpressStatusItemUM(text = stringReference("Done"), state = ExpressStatusItemState.Done), + ExpressStatusItemUM(text = stringReference("Active"), state = ExpressStatusItemState.Active), + ExpressStatusItemUM(text = stringReference("Warning"), state = ExpressStatusItemState.Warning), + ExpressStatusItemUM(text = stringReference("Error"), state = ExpressStatusItemState.Error), + ExpressStatusItemUM(text = stringReference("Default"), state = ExpressStatusItemState.Default), + ), + ) + + TangemThemePreview { + ExpressStatusBlock(state = state) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusNotificationBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusNotificationBlock.kt new file mode 100644 index 0000000000..b93ad58334 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusNotificationBlock.kt @@ -0,0 +1,32 @@ +package com.tangem.common.ui.expressStatus + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.tangem.common.ui.notifications.ExpressNotificationsUM +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.components.notifications.Notification +import com.tangem.core.ui.res.TangemTheme + +@Composable +fun ExpressStatusNotificationBlock(state: NotificationUM?) { + AnimatedContent( + targetState = state, + modifier = Modifier.padding(top = 12.dp), + label = "Express Status Notification Change", + ) { notification -> + if (notification?.config != null) { + Notification( + config = notification.config, + iconTint = when (state) { + is ExpressNotificationsUM.NeedVerification -> TangemTheme.colors.icon.attention + is ExpressNotificationsUM.FailedByProvider -> TangemTheme.colors.icon.warning + else -> null + }, + containerColor = TangemTheme.colors.background.action, + ) + } + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt new file mode 100644 index 0000000000..6fcb02cc30 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt @@ -0,0 +1,54 @@ +package com.tangem.common.ui.expressStatus.state + +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Stable +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.ImmutableList + +/** + * UI data holder for express status block + * + * @property title block title + * @property link provider web link + * @property statuses list of possible and active statuses + */ +data class ExpressStatusUM( + val title: TextReference, + val link: ExpressLinkUM, + val statuses: ImmutableList, +) + +/** + * Provider web link for express status block. + * [Empty] if no link needed + * [Content] if link is provided and displayed + */ +@Stable +sealed class ExpressLinkUM { + data object Empty : ExpressLinkUM() + data class Content( + @DrawableRes val icon: Int, + val text: TextReference, + val onClick: () -> Unit, + ) : ExpressLinkUM() +} + +/** + * Single status item in express status block + */ +data class ExpressStatusItemUM( + val text: TextReference, + val state: ExpressStatusItemState, +) + +/** + * Available status states for express status block + */ +enum class ExpressStatusItemState { + Active, + Default, + Done, + Warning, + Error, + ; +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/ExpressNotificationsUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/ExpressNotificationsUM.kt new file mode 100644 index 0000000000..09c66ed8c8 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/ExpressNotificationsUM.kt @@ -0,0 +1,28 @@ +package com.tangem.common.ui.notifications + +import com.tangem.common.ui.R +import com.tangem.core.ui.components.notifications.NotificationConfig +import com.tangem.core.ui.extensions.resourceReference + +object ExpressNotificationsUM { + + data class NeedVerification(val onGoToProviderClick: () -> Unit) : NotificationUM.Warning( + title = resourceReference(R.string.express_exchange_notification_verification_title), + subtitle = resourceReference(R.string.express_exchange_notification_verification_text), + iconResId = R.drawable.ic_alert_triangle_20, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.common_go_to_provider), + onClick = onGoToProviderClick, + ), + ) + + data class FailedByProvider(val onGoToProviderClick: () -> Unit) : NotificationUM.Error( + title = resourceReference(R.string.express_exchange_notification_failed_title), + subtitle = resourceReference(R.string.express_exchange_notification_failed_text), + iconResId = R.drawable.ic_alert_circle_24, + buttonState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.common_go_to_provider), + onClick = onGoToProviderClick, + ), + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt b/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt index 5296313d28..bebd090e66 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt @@ -5,8 +5,11 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme /** @@ -20,15 +23,16 @@ import com.tangem.core.ui.res.TangemTheme @Composable fun FooterContainer( modifier: Modifier = Modifier, - footer: String? = null, + footer: TextReference? = null, footerTopPadding: Dp = TangemTheme.dimens.spacing8, content: @Composable () -> Unit, ) { Column(modifier = modifier) { content() AnimatedVisibility(visible = footer != null) { + val footerWrapped = remember(this) { requireNotNull(footer) } Text( - text = footer.orEmpty(), + text = footerWrapped.resolveReference(), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, modifier = Modifier diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt index 1af5a5154a..65ad8d5140 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt @@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.core.ui.components.inputrow.InputRowEnterAmount import com.tangem.core.ui.components.inputrow.InputRowEnterInfoAmount -import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.state.fields.SendTextField @@ -41,7 +40,7 @@ internal fun SendCustomFee( repeat(customValues.size) { index -> val value = customValues[index] FooterContainer( - footer = value.footer.resolveReference(), + footer = value.footer, ) { if (value.label != null) { InputRowEnterInfoAmount( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index 013b9e7258..ea78244e14 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -21,8 +21,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.core.ui.components.containers.FooterContainer import com.tangem.core.ui.components.inputrow.InputRowRecipient +import com.tangem.core.ui.extensions.orMaskWithStars import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.send.impl.R @@ -32,8 +36,6 @@ import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub -import com.tangem.core.ui.components.containers.FooterContainer -import com.tangem.core.ui.extensions.orMaskWithStars import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import kotlinx.collections.immutable.ImmutableList @@ -107,7 +109,7 @@ private fun LazyListScope.addressItem( ) { item(key = ADDRESS_FIELD_KEY) { FooterContainer( - footer = stringResource(R.string.send_recipient_address_footer, network), + footer = resourceReference(R.string.send_recipient_address_footer, wrappedList(network)), ) { InputRowRecipient( value = address.value, @@ -137,7 +139,7 @@ private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onM value = memoField.value, label = memoField.label, placeholder = placeholder, - footer = stringResource(R.string.send_recipient_memo_footer), + footer = resourceReference(R.string.send_recipient_memo_footer), onValueChange = memoField.onValueChange, onPasteClick = onMemoChange, modifier = Modifier.padding(top = TangemTheme.dimens.spacing20), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt index 762dc7f7eb..cc33643eab 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt @@ -27,7 +27,7 @@ internal fun TextFieldWithPaste( onValueChange: (String) -> Unit, onPasteClick: (String) -> Unit, modifier: Modifier = Modifier, - footer: String? = null, + footer: TextReference? = null, labelStyle: TextStyle = TangemTheme.typography.body2, error: TextReference? = null, isError: Boolean = false, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt index 47508d351e..8d262a4334 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt @@ -19,6 +19,7 @@ internal sealed interface ExchangeStatusNotifications { sealed class CommonNotification(val config: NotificationConfig) : ExchangeStatusNotifications + @Deprecated("Use one in ExpressNotificationsUM") data class NeedVerification(val onGoToProviderClick: () -> Unit) : CommonNotification( config = NotificationConfig( title = resourceReference(R.string.express_exchange_notification_verification_title), @@ -31,6 +32,7 @@ internal sealed interface ExchangeStatusNotifications { ), ) + @Deprecated("Use one in ExpressNotificationsUM") data class Failed(val onGoToProviderClick: () -> Unit) : CommonNotification( config = NotificationConfig( title = resourceReference(R.string.express_exchange_notification_failed_title), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt index ec2a2b3cfb..7831a8aba3 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt @@ -30,6 +30,7 @@ import com.tangem.features.tokendetails.impl.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +@Deprecated("Use ExpressStatusBlock from common") @Composable internal fun ExchangeStatusBlock( statuses: ImmutableList, From 7d864a96e45e65c4c2690984eeaad2301005b466 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Nov 2024 17:58:46 +0500 Subject: [PATCH 49/99] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-es/strings.xml | 8 +- core/res/src/main/res/values-fr/strings.xml | 8 +- core/res/src/main/res/values-ja/strings.xml | 14 +- core/res/src/main/res/values-ru/strings.xml | 4 +- core/res/src/main/res/values/strings.xml | 3 + .../component/OnrampSuccessComponent.kt | 2 +- features/onramp/impl/build.gradle.kts | 2 +- .../success/DefaultOnrampSuccessComponent.kt | 8 +- .../entity/OnrampSuccessClickIntents.kt | 5 + .../entity/OnrampSuccessComponentUM.kt | 16 +- .../SetOnrampSuccessContentConverter.kt | 194 +++++++++++++++ .../OnrampSuccessComponentUMPreviewData.kt | 50 ++++ .../model/OnrampSuccessComponentModel.kt | 78 ++++++- .../ui/OnrampSuccessComponentContent.kt | 220 ++++++++++++++++++ 15 files changed, 585 insertions(+), 29 deletions(-) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessClickIntents.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/conterter/SetOnrampSuccessContentConverter.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/previewdata/OnrampSuccessComponentUMPreviewData.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 65a4990969..8fe310b7d9 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -568,7 +568,7 @@ Keine verfügbaren Anbieter für diese Währung Bezahlen mit Du kannst Deine Transaktion beim Drittanbieter %s abschließen. - Umleitung auf %s … + Umleitung auf %s… Unsere Dienstleistungen sind in diesem Land nicht verfügbar Änder oder bestätige bitte Dein Wohnsitz wurde identifiziert als diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index ee04d97a2f..b77f4a6456 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -858,14 +858,14 @@ Saldos mostrados Cancelar Esta operación no está disponible actualmente. Por favor, inténtalo de nuevo más tarde. - Comprar %s no está disponible en este momento. Por favor revise sus actualizaciones. + Comprar %s no está disponible con los proveedores actuales pero estamos trabajando para agregar más opciones. No tiene fondos para vender. Recargue su cuenta para poder vender fondos desde ella. No tienes fondos para enviar. Recarga tu cuenta para poder enviar fondos desde ella. - El servicio de intercambio %s no está disponible en este momento. Por favor consulte nuestras actualizaciones. + El servicio de intercambio %s no está compatible con los proveedores actuales pero estamos trabajando para agregar más opciones. La venta de fondos estará disponible una vez que la(s) transacción(es) pendiente(s) en la red %s se complete El envío de fondos estará disponible una vez que se completen las transacciones pendientes en la red %s. - La venta de %s no está disponible en este momento. Por favor consulte nuestras actualizaciones. - El staking %s no está disponible en este momento. Por favor consulte nuestras actualizaciones. + Vender %s no es compatible con los proveedores actuales, pero estamos trabajando para agregar más opciones. + El staking de %s no es compatible con los proveedores actuales, pero estamos trabajando para agregar más opciones. Generar XPUB Ocultar Está a punto de ocultar este token de la pantalla principal. Puede volver a agregarlo en cualquier momento a través de la página de gestión de tokens. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index c9983efdb7..346d3081e5 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -850,14 +850,14 @@ Soldes affichés Annuler Cette opération est actuellement indisponible. Veuillez réessayer plus tard. - L\'achat de %s n\'est pas disponible pour le moment. Veuillez vérifier vos mises à jour. + L\'achat de %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter davantage d\'options. Vous n\'avez pas de fonds à vendre. Renflouez votre compte pour pouvoir vendre des fonds à partir de celui-ci. Vous n\'avez pas de fonds à envoyer. Renflouez votre compte pour pouvoir envoyer des fonds à partir de celui-ci. - Le service d\'échange %s n\'est pas disponible pour le moment. Veuillez consulter nos mises à jour. + Le service d\'échange %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter plus d\'options. La vente de fonds sera disponible une fois que la ou les transactions en attente dans le réseau %s seront terminées L\'envoi de fonds sera disponible une fois la ou les transactions en attente dans le réseau %s terminées. - La vente de %s n\'est pas disponible pour le moment. Veuillez consulter nos mises à jour. - Le staking %s n’est pas disponible pour le moment. Veuillez consulter nos mises à jour. + L\'achat de %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter plus d\'options. + Le staking de %s n\'est pas pris en charge par les fournisseurs actuels, mais nous travaillons à ajouter plus d\'options. Générer XPUB Masquer Vous êtes sur le point de masquer ce jeton de l\'écran principal. Vous pouvez le rajouter à tout moment via la page de gestion des jetons. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 3d8d8d15d5..f4e7cfccfe 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -830,7 +830,7 @@ 金額には以下が含まれます: \n • サービス プロバイダーの手数料\n • 取引所からユーザーのアドレスに%1$sを送金するためのネットワーク手数料。 \n\nプロバイダーのスリッページは最大%2$sです この金額には、サービスプロバイダーの手数料が含まれています。 金額にはサービスプロバイダーの手数料が含まれます。 \n\nプロバイダーのスリッページは最大%s です - 手数料 + 情報 すべての分散型取引所は、スマートコントラクトがあなたの許可なくウォレットにアクセスするのを防ぐために承認を必要とします。設計上、スマートコントラクトは承認なしでトークンにアクセスできません。トークンを「ロック解除」することで、あなたは1-inchのスマートコントラクトがトークンを使うことを承認します。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためのガス料金(あなたが支払う)を受け取ります。承認後、トークンを交換することができます。 承認 手数料見積りエラーです。サポートにフィードバックをお送りください。 @@ -846,14 +846,14 @@ 残高表示 元に戻す この操作は現在利用できません。しばらくしてからもう一度お試しください。 - 現在、 %sの買付はご利用いただけません。アップデート情報をご確認ください。 + %sの買付は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 売却できる資金がありません。アカウントに入金して、売却できるようにしてください。 送金する資金がありません。アカウントに入金して、送金できるようにしてください。 - 現在、 %sの交換はご利用いただけません。アップデート情報を確認してください。 + %sのスワップは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 ネットワーク%s内の保留中の取引が完了すると、資金の売却が可能になります。 ネットワーク%s内の保留中の取引が完了すると、送金が可能になります。 - 現在、 %sの売却はご利用いただけません。アップデート情報をご確認ください。 - %s のステーキングは現在ご利用いただけません。最新情報をご確認ください。 + %sの売却は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 + %sのステーキングは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 XPUBを生成する 非表示 このトークンをメイン画面から非表示にします。トークンの管理ページからいつでも再度追加できます。 @@ -946,7 +946,7 @@ %sネットワーク アドレスがクリップボードにコピーされました インターネット接続がありません - 今すぐ10 %オフで購入 + 今すぐ10 %%オフで購入 1.3万種類以上の暗号資産にアクセス。ワンタップで買付、売却、スワップ、ステーキングが可能です。\nバックアップ用に最大3枚のカードを連携できます。 Tangemウォレットを見る ウォレット設定 @@ -1019,7 +1019,7 @@ Solanaネットワークが混雑しています。2分以内に取引が完了しない場合は、再度取引を繰り返してください。 Solanaネットワークアラート Solana ネットワークは 2 日ごとに%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 9a43af9607..76ba8cfddf 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -581,7 +581,7 @@ Для данной валюты нет доступных провайдеров Оплата с Вы сможете завершить транзакцию через сервис стороннего провайдера, %s - Переход к %s ... + Переход к %s... Наши сервисы недоступны в данной стране Настройки Через @@ -966,7 +966,7 @@ Нет соединения с интернетом Получить с 10%% скидкой Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменяйте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. - Откройте Tangem Wallet + Откройте Tangem Wallet Настройки кошелька Tangem Используйте %s или отсканируйте карту/кольцо, чтобы разблокировать доступ к вашему кошельку diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 6978552ae0..6241677828 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -294,6 +294,9 @@ Unavailable for this pair Permission Required Recommended + Bought %s + Buying %s + Buying %s... No tokens found. Please try another request ID: %s Transaction ID copied diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt index 70b6e543c4..ceed88f9c5 100644 --- a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt @@ -5,7 +5,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent interface OnrampSuccessComponent : ComposableContentComponent { - data class Params(val txId: String?) + data class Params(val txId: String) interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 314c0f9529..c97691d146 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(projects.core.decompose) implementation(projects.core.ui) implementation(projects.core.featuretoggles) + implementation(projects.core.navigation) /** Project - Common */ implementation(projects.common.routing) @@ -42,7 +43,6 @@ dependencies { /** DI */ implementation(deps.hilt.android) - implementation(project(":common:ui")) kapt(deps.hilt.kapt) /** AndroidX */ diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt index 0f5787d3b3..d1b329d10b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt @@ -1,5 +1,6 @@ package com.tangem.features.onramp.success +import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -8,6 +9,7 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.features.onramp.component.OnrampSuccessComponent import com.tangem.features.onramp.success.model.OnrampSuccessComponentModel +import com.tangem.features.onramp.success.ui.OnrampSuccessComponentContent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -23,7 +25,11 @@ internal class DefaultOnrampSuccessComponent @AssistedInject constructor( override fun Content(modifier: Modifier) { val state by model.state.collectAsState() - // todo onramp [REDACTED_JIRA] + BackHandler(onBack = router::pop) + OnrampSuccessComponentContent( + state = state, + onBackClick = router::pop, + ) } @AssistedFactory diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessClickIntents.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessClickIntents.kt new file mode 100644 index 0000000000..7c7565aef5 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessClickIntents.kt @@ -0,0 +1,5 @@ +package com.tangem.features.onramp.success.entity + +interface OnrampSuccessClickIntents { + fun goToProviderClick(providerLink: String) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt index 3f0bea7160..71f80e2f05 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt @@ -1,16 +1,22 @@ package com.tangem.features.onramp.success.entity +import com.tangem.common.ui.expressStatus.state.ExpressStatusUM +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.extensions.TextReference + sealed class OnrampSuccessComponentUM { data object Loading : OnrampSuccessComponentUM() data class Content( val txId: String, - val createdAt: Long, - val providerId: String, + val timestamp: Long, val currencyImageUrl: String, - val fromAmount: String, - val toAmount: String, - val onBackClick: () -> Unit, + val fromAmount: TextReference, + val toAmount: TextReference, + val statusBlock: ExpressStatusUM, + val providerName: TextReference, + val providerImageUrl: String, + val notification: NotificationUM?, ) : OnrampSuccessComponentUM() } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/conterter/SetOnrampSuccessContentConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/conterter/SetOnrampSuccessContentConverter.kt new file mode 100644 index 0000000000..435ced42ba --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/conterter/SetOnrampSuccessContentConverter.kt @@ -0,0 +1,194 @@ +package com.tangem.features.onramp.success.entity.conterter + +import com.tangem.common.ui.expressStatus.state.ExpressLinkUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusUM +import com.tangem.common.ui.notifications.ExpressNotificationsUM +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf +import org.joda.time.DateTime + +internal class SetOnrampSuccessContentConverter( + private val cryptoCurrency: CryptoCurrency, + private val transaction: OnrampTransaction, + private val goToProviderClick: (String) -> Unit, +) : Converter { + override fun convert(value: OnrampStatus): OnrampSuccessComponentUM { + return OnrampSuccessComponentUM.Content( + txId = value.txId, + timestamp = DateTime.parse(value.createdAt).millis, + currencyImageUrl = transaction.fromCurrency.image, + fromAmount = stringReference( + transaction.fromAmount.format { + fiat( + fiatCurrencyCode = transaction.fromCurrency.name, + fiatCurrencySymbol = transaction.fromCurrency.code, + ) + }, + ), + toAmount = stringReference( + transaction.toAmount.format { + crypto(cryptoCurrency) + }, + ), + providerName = stringReference(transaction.providerName), + providerImageUrl = transaction.providerImageUrl, + statusBlock = convertStatuses(value.status, value.externalTxUrl), + notification = getNotification(value.status, value.externalTxUrl), + ) + } + + private fun getNotification(status: OnrampStatus.Status, externalTxUrl: String?): NotificationUM? { + if (externalTxUrl == null) return null + return when (status) { + OnrampStatus.Status.Verifying -> { + ExpressNotificationsUM.NeedVerification { + goToProviderClick(externalTxUrl) + } + } + OnrampStatus.Status.Failed -> { + ExpressNotificationsUM.FailedByProvider { + goToProviderClick(externalTxUrl) + } + } + else -> null + } + } + + private fun convertStatuses(status: OnrampStatus.Status, externalTxUrl: String?): ExpressStatusUM { + val statuses = with(status) { + persistentListOf( + getAwaitingDepositItem(), + getPaymentProcessingItem(), + getBuyingItem(), + getSendingItem(), + ) + } + + return ExpressStatusUM( + title = resourceReference(R.string.common_transaction_status), + link = getStatusLink(status, externalTxUrl), + statuses = statuses, + ) + } + + private fun OnrampStatus.Status.getAwaitingDepositItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.WaitingForPayment.order -> { + resourceReference(R.string.express_exchange_status_receiving) + } + this == OnrampStatus.Status.WaitingForPayment -> { + resourceReference(R.string.express_exchange_status_receiving_active) + } + else -> { + resourceReference(R.string.express_exchange_status_received) + } + }, + state = getStatusState(OnrampStatus.Status.WaitingForPayment), + ) + + private fun OnrampStatus.Status.getPaymentProcessingItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.PaymentProcessing.order -> { + resourceReference(R.string.express_exchange_status_confirming) + } + this == OnrampStatus.Status.PaymentProcessing -> { + resourceReference(R.string.express_exchange_status_confirming_active) + } + this == OnrampStatus.Status.Verifying -> { + resourceReference(R.string.express_exchange_status_verifying) + } + this == OnrampStatus.Status.Failed -> { + resourceReference(R.string.express_exchange_status_failed) + } + else -> resourceReference(R.string.express_exchange_status_confirmed) + }, + state = when { + order < OnrampStatus.Status.PaymentProcessing.order -> { + ExpressStatusItemState.Default + } + this == OnrampStatus.Status.PaymentProcessing -> { + ExpressStatusItemState.Active + } + this == OnrampStatus.Status.Verifying -> { + ExpressStatusItemState.Warning + } + this == OnrampStatus.Status.Failed -> { + ExpressStatusItemState.Error + } + else -> { + ExpressStatusItemState.Done + } + }, + ) + + private fun OnrampStatus.Status.getBuyingItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.Paid.order -> { + resourceReference(R.string.express_status_buying, wrappedList(cryptoCurrency.name)) + } + this == OnrampStatus.Status.Paid -> { + resourceReference(R.string.express_status_buying_active, wrappedList(cryptoCurrency.name)) + } + else -> { + resourceReference(R.string.express_status_bought, wrappedList(cryptoCurrency.name)) + } + }, + state = getStatusState(OnrampStatus.Status.Paid), + ) + + private fun OnrampStatus.Status.getSendingItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.Sending.order -> { + resourceReference(R.string.express_exchange_status_sending, wrappedList(cryptoCurrency.name)) + } + this == OnrampStatus.Status.Sending -> { + resourceReference( + R.string.express_exchange_status_sending_active, + wrappedList(cryptoCurrency.name), + ) + } + else -> { + resourceReference(R.string.express_exchange_status_sent, wrappedList(cryptoCurrency.name)) + } + }, + state = getStatusState(OnrampStatus.Status.Sending), + ) + + private fun getStatusLink(status: OnrampStatus.Status, externalTxUrl: String?): ExpressLinkUM { + if (externalTxUrl == null) return ExpressLinkUM.Empty + return when (status) { + OnrampStatus.Status.Verifying, + OnrampStatus.Status.Failed, + -> { + ExpressLinkUM.Content( + icon = R.drawable.ic_arrow_top_right_24, + text = resourceReference(R.string.common_go_to_provider), + onClick = { + goToProviderClick(externalTxUrl) + }, + ) + } + else -> ExpressLinkUM.Empty + } + } + + private fun OnrampStatus.Status.getStatusState(targetState: OnrampStatus.Status) = when { + order < targetState.order -> ExpressStatusItemState.Default + this == targetState -> ExpressStatusItemState.Active + else -> ExpressStatusItemState.Done + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/previewdata/OnrampSuccessComponentUMPreviewData.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/previewdata/OnrampSuccessComponentUMPreviewData.kt new file mode 100644 index 0000000000..d7e355ba63 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/previewdata/OnrampSuccessComponentUMPreviewData.kt @@ -0,0 +1,50 @@ +package com.tangem.features.onramp.success.entity.previewdata + +import com.tangem.common.ui.expressStatus.state.ExpressLinkUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusUM +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.success.entity.OnrampSuccessComponentUM +import kotlinx.collections.immutable.persistentListOf +import org.joda.time.DateTime + +internal data object OnrampSuccessComponentUMPreviewData { + + val loadingState = OnrampSuccessComponentUM.Loading + + val contentState = OnrampSuccessComponentUM.Content( + txId = "b2894851-7b63-4f56-bd75-e408f1dcba31", + timestamp = DateTime.parse("2023-09-20T13:45:10.868Z").millis, + providerName = stringReference("1Inch"), + providerImageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/1INCH1024.png", + fromAmount = stringReference("100 USDT"), + toAmount = stringReference("99.99 $"), + currencyImageUrl = "", + notification = null, + statusBlock = ExpressStatusUM( + title = resourceReference(R.string.express_exchange_status_title), + link = ExpressLinkUM.Empty, + statuses = persistentListOf( + ExpressStatusItemUM( + text = stringReference("Deposit received"), + state = ExpressStatusItemState.Done, + ), + ExpressStatusItemUM( + text = stringReference("Confirmed"), + state = ExpressStatusItemState.Done, + ), + ExpressStatusItemUM( + text = stringReference("Buying Bitcoin..."), + state = ExpressStatusItemState.Active, + ), + ExpressStatusItemUM( + text = stringReference("Sending to you"), + state = ExpressStatusItemState.Default, + ), + ), + ), + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt index bdda90effd..6e925f143c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt @@ -1,25 +1,97 @@ package com.tangem.features.onramp.success.model +import arrow.core.getOrElse import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.navigation.url.UrlOpener +import com.tangem.domain.onramp.GetOnrampStatusUseCase +import com.tangem.domain.onramp.GetOnrampTransactionUseCase +import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.GetCryptoCurrencyUseCase import com.tangem.features.onramp.component.OnrampSuccessComponent +import com.tangem.features.onramp.success.entity.OnrampSuccessClickIntents import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM +import com.tangem.features.onramp.success.entity.conterter.SetOnrampSuccessContentConverter import com.tangem.utils.coroutines.CoroutineDispatcherProvider 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 timber.log.Timber import javax.inject.Inject +@Suppress("LongParameterList") internal class OnrampSuccessComponentModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, + private val urlOpener: UrlOpener, + private val getOnrampTransactionUseCase: GetOnrampTransactionUseCase, + private val getOnrampStatusUseCase: GetOnrampStatusUseCase, + private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, + private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, paramsContainer: ParamsContainer, -) : Model() { +) : Model(), OnrampSuccessClickIntents { - // todo onramp remove in [REDACTED_JIRA] - @Suppress("UnusedPrivateMember") private val params: OnrampSuccessComponent.Params = paramsContainer.require() private val _state: MutableStateFlow = MutableStateFlow( value = OnrampSuccessComponentUM.Loading, ) + val state: StateFlow get() = _state.asStateFlow() + + init { + loadData() + } + + override fun goToProviderClick(providerLink: String) { + urlOpener.openUrl(providerLink) + } + + private fun loadData() { + modelScope.launch { + getOnrampTransactionUseCase(txId = params.txId) + .fold( + ifLeft = { + Timber.e(it.toString()) + }, + ifRight = { transaction -> + loadTransactionStatus(transaction) + }, + ) + } + } + + private suspend fun loadTransactionStatus(transaction: OnrampTransaction) { + val cryptoCurrencies = getCryptoCurrencyUseCase( + transaction.userWalletId, + transaction.toCurrencyId, + ).getOrElse { error("Crypto currency not found") } + + getOnrampStatusUseCase(txId = params.txId) + .fold( + ifLeft = { + Timber.e(it.toString()) + }, + ifRight = { status -> + _state.update { + SetOnrampSuccessContentConverter( + cryptoCurrency = cryptoCurrencies, + transaction = transaction, + goToProviderClick = ::goToProviderClick, + ).convert(status) + } + removeTransactionIfTerminalStatus(status) + }, + ) + } + + private fun removeTransactionIfTerminalStatus(status: OnrampStatus) { + modelScope.launch { + if (status.status.isTerminal()) { + onrampRemoveTransactionUseCase(status.txId) + } + } + } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt new file mode 100644 index 0000000000..e021c732e7 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt @@ -0,0 +1,220 @@ +package com.tangem.features.onramp.success.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Scaffold +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.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.common.ui.expressStatus.ExpressStatusBlock +import com.tangem.common.ui.expressStatus.ExpressStatusNotificationBlock +import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.components.containers.FooterContainer +import com.tangem.core.ui.components.transactions.TransactionDoneTitle +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.wrappedList +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.utils.DateTimeFormatters +import com.tangem.core.ui.utils.WindowInsetsZero +import com.tangem.core.ui.utils.toTimeFormat +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM +import com.tangem.features.onramp.success.entity.previewdata.OnrampSuccessComponentUMPreviewData + +@Composable +internal fun OnrampSuccessComponentContent( + state: OnrampSuccessComponentUM, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + if (state !is OnrampSuccessComponentUM.Content) { + Box(modifier.background(TangemTheme.colors.background.secondary)) + } else { + Content( + state = state, + onBackClick = onBackClick, + modifier = modifier, + ) + } +} + +@Composable +private fun Content(state: OnrampSuccessComponentUM.Content, onBackClick: () -> Unit, modifier: Modifier = Modifier) { + Scaffold( + modifier = modifier + .background(TangemTheme.colors.background.secondary) + .systemBarsPadding(), + topBar = { + TangemTopAppBar( + startButton = TopAppBarButtonUM( + iconRes = R.drawable.ic_close_24, + onIconClicked = onBackClick, + ), + ) + }, + bottomBar = { + PrimaryButton( + text = stringResource(R.string.common_close), + onClick = onBackClick, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + }, + contentWindowInsets = WindowInsetsZero, + ) { scaffoldPaddings -> + Column( + modifier = Modifier + .background(TangemTheme.colors.background.secondary) + .padding(scaffoldPaddings) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(16.dp), + ) { + TransactionDoneTitle( + title = resourceReference(R.string.common_in_progress), + subtitle = resourceReference( + R.string.send_date_format, + wrappedList( + state.timestamp.toTimeFormat(DateTimeFormatters.dateFormatter), + state.timestamp.toTimeFormat(), + ), + ), + ) + SpacerH24() + AmountBlock(state) + SpacerH12() + FooterContainer( + footer = resourceReference(R.string.onramp_transaction_status_footer_text), + ) { + ExpressStatusBlock(state.statusBlock) + } + ExpressStatusNotificationBlock( + state.notification, + ) + } + } +} + +@Composable +private fun AmountBlock(state: OnrampSuccessComponentUM.Content) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action) + .padding(16.dp), + ) { + val iconModifier = Modifier.size(size = 40.dp) + SubcomposeAsyncImage( + modifier = iconModifier, + model = ImageRequest.Builder(context = LocalContext.current) + .data(state.currencyImageUrl) + .crossfade(enable = true) + .allowHardware(false) + .build(), + loading = { CircleShimmer(modifier = iconModifier) }, + error = { }, + contentDescription = null, + ) + ResizableText( + text = state.fromAmount.resolveReference(), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + ) + Text( + text = state.toAmount.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + ) + SpacerH12() + AmountProviderBlock( + state.providerName, + state.providerImageUrl, + ) + } +} + +@Composable +private fun AmountProviderBlock(providerName: TextReference, providerImageUrl: String, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + text = stringResource(R.string.common_with), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + ) + SubcomposeAsyncImage( + modifier = Modifier.size(size = 16.dp), + model = ImageRequest.Builder(context = LocalContext.current) + .data(providerImageUrl) + .crossfade(enable = true) + .allowHardware(false) + .build(), + loading = { CircleShimmer() }, + error = { }, + contentDescription = null, + ) + Text( + text = providerName.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360, heightDp = 720) +@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun OnrampSuccessComponentContent_Preview( + @PreviewParameter(OnrampSuccessComponentContentPreviewProvider::class) + data: OnrampSuccessComponentUM, +) { + TangemThemePreview { + OnrampSuccessComponentContent( + state = data, + onBackClick = {}, + ) + } +} + +private class OnrampSuccessComponentContentPreviewProvider : + PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + OnrampSuccessComponentUMPreviewData.contentState, + ) +} +// endregion \ No newline at end of file From af74eec36420cc67aecefddf24100eba6ae885c5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Nov 2024 10:31:02 +0500 Subject: [PATCH 50/99] Updated on 2026-08-14 --- .../domain/tokens/repository/MockCurrenciesRepository.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt index f68979c6f4..b23e4a8726 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt @@ -100,6 +100,14 @@ internal class MockCurrenciesRepository( return token } + override suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: String): CryptoCurrency { + val token = token.getOrElse { e -> throw e } + + require(token.id.value == id) + + return token + } + override suspend fun getNetworkCoin( userWalletId: UserWalletId, networkId: Network.ID, From 851d9ffea96512f4c0a621eef91ef325a1957850 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Nov 2024 13:36:28 +0500 Subject: [PATCH 51/99] Updated on 2026-08-14 --- .../send/impl/presentation/state/fee/FeeCalculation.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index 2171c34904..a13f13cc19 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -68,7 +68,11 @@ internal fun checkIfFeeTooHigh(feeSelectorState: FeeSelectorState.Content, onSho val highValue = multipleFees.priority.amount.value ?: return false val customAmount = feeSelectorState.customValues.firstOrNull() ?: return false val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - val diff = customValue / highValue + val diff = if (highValue > BigDecimal.ZERO) { + customValue / highValue + } else { + BigDecimal.ZERO + } val isShow = feeSelectorState.selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF if (isShow) onShow(diff.parseBigDecimal(ZERO_DECIMALS, RoundingMode.HALF_UP)) return isShow From 9f65e1b80c86898aaa7824803178a6ad3be3ca0d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Nov 2024 14:34:19 +0500 Subject: [PATCH 52/99] Updated on 2026-08-14 --- .../tap/di/domain/OnrampDomainModule.kt | 6 + .../tangem/datasource/di/OnrampStoreModule.kt | 21 ++++ .../DefaultOnrampPaymentMethodsStore.kt | 12 ++ .../local/onramp/OnrampPaymentMethodsStore.kt | 12 ++ .../data/onramp/DefaultOnrampRepository.kt | 31 +++++ .../converters/PaymentMethodConverter.kt | 13 ++ .../tangem/data/onramp/di/OnrampDataModule.kt | 3 + .../onramp/model/OnrampPaymentMethod.kt | 10 ++ .../onramp/CheckOnrampAvailabilityUseCase.kt | 1 + .../onramp/GetOnrampPaymentMethodsUseCase.kt | 12 ++ .../onramp/repositories/OnrampRepository.kt | 3 + .../model/ConfirmResidencyModel.kt | 9 +- .../DefaultSelectPaymentMethodComponent.kt | 45 +++++++ .../SelectPaymentMethodComponent.kt | 16 +++ .../di/SelectPaymentMethodComponentModule.kt | 20 +++ .../paymentmethod/entity/PaymentMethodUM.kt | 8 ++ .../entity/PaymentMethodsBottomSheetConfig.kt | 9 ++ .../ui/SelectPaymentMethodBottomSheet.kt | 118 ++++++++++++++++++ 18 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/onramp/DefaultOnrampPaymentMethodsStore.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/onramp/OnrampPaymentMethodsStore.kt create mode 100644 data/onramp/src/main/java/com/tangem/data/onramp/converters/PaymentMethodConverter.kt create mode 100644 domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampPaymentMethod.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/DefaultSelectPaymentMethodComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/SelectPaymentMethodComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/di/SelectPaymentMethodComponentModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/entity/PaymentMethodUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/entity/PaymentMethodsBottomSheetConfig.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/ui/SelectPaymentMethodBottomSheet.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 2c276a7117..d21153b84b 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 @@ -96,4 +96,10 @@ internal object OnrampDomainModule { ): OnrampSaveTransactionUseCase { return OnrampSaveTransactionUseCase(onrampTransactionRepository) } + + @Provides + @Singleton + fun provideGetOnrampPaymentMethodsUseCase(onrampRepository: OnrampRepository): GetOnrampPaymentMethodsUseCase { + return GetOnrampPaymentMethodsUseCase(onrampRepository) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt new file mode 100644 index 0000000000..8b7fb87fa2 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.di + +import com.tangem.datasource.local.datastore.RuntimeDataStore +import com.tangem.datasource.local.onramp.DefaultOnrampPaymentMethodsStore +import com.tangem.datasource.local.onramp.OnrampPaymentMethodsStore +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 OnrampStoreModule { + + @Provides + @Singleton + fun provideOnrampPaymentMethodsStore(): OnrampPaymentMethodsStore { + return DefaultOnrampPaymentMethodsStore(dataStore = RuntimeDataStore()) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/DefaultOnrampPaymentMethodsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/DefaultOnrampPaymentMethodsStore.kt new file mode 100644 index 0000000000..3a4fa1b949 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/DefaultOnrampPaymentMethodsStore.kt @@ -0,0 +1,12 @@ +package com.tangem.datasource.local.onramp + +import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO +import com.tangem.datasource.local.datastore.core.StringKeyDataStore +import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator + +internal class DefaultOnrampPaymentMethodsStore( + dataStore: StringKeyDataStore>, +) : OnrampPaymentMethodsStore, StringKeyDataStoreDecorator>(dataStore) { + + override fun provideStringKey(key: String): String = key +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/OnrampPaymentMethodsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/OnrampPaymentMethodsStore.kt new file mode 100644 index 0000000000..a6847ae671 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/OnrampPaymentMethodsStore.kt @@ -0,0 +1,12 @@ +package com.tangem.datasource.local.onramp + +import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO + +interface OnrampPaymentMethodsStore { + + suspend fun getSyncOrNull(key: String): List? + + suspend fun store(key: String, value: List) + + suspend fun contains(key: String): Boolean +} \ No newline at end of file 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 ef6cd4523e..4145803c20 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 @@ -1,12 +1,15 @@ package com.tangem.data.onramp +import com.tangem.data.common.api.safeApiCall import com.tangem.data.onramp.converters.CountryConverter import com.tangem.data.onramp.converters.CurrencyConverter import com.tangem.data.onramp.converters.StatusConverter +import com.tangem.data.onramp.converters.PaymentMethodConverter 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.onramp.OnrampPaymentMethodsStore import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObject @@ -15,21 +18,25 @@ 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.model.OnrampStatus +import com.tangem.domain.onramp.model.OnrampPaymentMethod 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 +import timber.log.Timber internal class DefaultOnrampRepository( private val onrampApi: OnrampApi, private val dispatchers: CoroutineDispatcherProvider, private val appPreferencesStore: AppPreferencesStore, + private val paymentMethodsStore: OnrampPaymentMethodsStore, ) : OnrampRepository { private val currencyConverter = CurrencyConverter() private val countryConverter = CountryConverter(currencyConverter) private val statusConverter = StatusConverter() + private val paymentMethodsConverter = PaymentMethodConverter() override suspend fun getCurrencies(): List = withContext(dispatchers.io) { onrampApi.getCurrencies() @@ -92,4 +99,28 @@ internal class DefaultOnrampRepository( .getObject(PreferencesKeys.ONRAMP_DEFAULT_COUNTRY) .map { it?.let(countryConverter::convert) } } + + override suspend fun fetchPaymentMethodsIfAbsent() { + if (paymentMethodsStore.contains(PAYMENT_METHODS_KEY)) return + + val response = safeApiCall( + call = { onrampApi.getPaymentMethods().bind() }, + onError = { + Timber.w(it, "Unable to fetch onramp payment methods") + throw it + }, + ) + paymentMethodsStore.store(PAYMENT_METHODS_KEY, response) + } + + override suspend fun getPaymentMethods(): List { + val paymentMethods = requireNotNull(paymentMethodsStore.getSyncOrNull(PAYMENT_METHODS_KEY)) { + "Onramp payment methods is absent in storage" + } + return paymentMethodsConverter.convertList(paymentMethods) + } + + private companion object { + const val PAYMENT_METHODS_KEY = "onramp_payment_methods" + } } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/PaymentMethodConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/PaymentMethodConverter.kt new file mode 100644 index 0000000000..b45273c12e --- /dev/null +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/PaymentMethodConverter.kt @@ -0,0 +1,13 @@ +package com.tangem.data.onramp.converters + +import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO +import com.tangem.domain.onramp.model.OnrampPaymentMethod +import com.tangem.utils.converter.Converter + +internal class PaymentMethodConverter : Converter { + override fun convert(value: PaymentMethodDTO): OnrampPaymentMethod = OnrampPaymentMethod( + id = value.id, + name = value.name, + imageUrl = value.image, + ) +} \ 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 fed11e54d0..3731a0773d 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 @@ -4,6 +4,7 @@ import com.tangem.data.onramp.DefaultOnrampErrorResolver import com.tangem.data.onramp.DefaultOnrampRepository import com.tangem.data.onramp.DefaultOnrampTransactionRepository import com.tangem.datasource.api.onramp.OnrampApi +import com.tangem.datasource.local.onramp.OnrampPaymentMethodsStore import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampRepository @@ -25,11 +26,13 @@ internal object OnrampDataModule { onrampApi: OnrampApi, dispatchers: CoroutineDispatcherProvider, appPreferencesStore: AppPreferencesStore, + paymentMethodsStore: OnrampPaymentMethodsStore, ): OnrampRepository { return DefaultOnrampRepository( onrampApi = onrampApi, dispatchers = dispatchers, appPreferencesStore = appPreferencesStore, + paymentMethodsStore = paymentMethodsStore, ) } diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampPaymentMethod.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampPaymentMethod.kt new file mode 100644 index 0000000000..0ddf36dbf6 --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampPaymentMethod.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.onramp.model + +import kotlinx.serialization.Serializable + +@Serializable +data class OnrampPaymentMethod( + val id: String, + val name: String, + val imageUrl: String, +) \ No newline at end of file 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 index 46af00376b..2e099164d9 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt @@ -8,6 +8,7 @@ class CheckOnrampAvailabilityUseCase(private val repository: OnrampRepository) { suspend operator fun invoke(): Either { return Either.catch { + repository.fetchPaymentMethodsIfAbsent() repository.getDefaultCountrySync()?.let { savedCountry -> return@catch if (savedCountry.onrampAvailable) { val currency = repository.getDefaultCurrencySync() ?: run { diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt new file mode 100644 index 0000000000..898bf3be19 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampPaymentMethod +import com.tangem.domain.onramp.repositories.OnrampRepository + +class GetOnrampPaymentMethodsUseCase(private val repository: OnrampRepository) { + + suspend operator fun invoke(): Either> { + return Either.catch { repository.getPaymentMethods() } + } +} \ 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 7e6dc7df12..1c269c1a75 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 @@ -3,6 +3,7 @@ package com.tangem.domain.onramp.repositories import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.domain.onramp.model.OnrampCurrency import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.model.OnrampPaymentMethod import kotlinx.coroutines.flow.Flow interface OnrampRepository { @@ -11,6 +12,7 @@ interface OnrampRepository { suspend fun getCountries(): List suspend fun getCountryByIp(): OnrampCountry suspend fun getStatus(txId: String): OnrampStatus + suspend fun fetchPaymentMethodsIfAbsent() // cache suspend fun saveDefaultCurrency(currency: OnrampCurrency) @@ -19,4 +21,5 @@ interface OnrampRepository { suspend fun saveDefaultCountry(country: OnrampCountry) suspend fun getDefaultCountrySync(): OnrampCountry? fun getDefaultCountry(): Flow + suspend fun getPaymentMethods(): List } \ No newline at end of file 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 25a7058831..d9afb5e94b 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 @@ -8,20 +8,23 @@ 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.domain.onramp.OnrampSaveDefaultCountryUseCase import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent +import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import javax.inject.Inject @ComponentScoped internal class ConfirmResidencyModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val router: Router, + private val saveDefaultCountryUseCase: OnrampSaveDefaultCountryUseCase, getOnrampCountryUseCase: GetOnrampCountryUseCase, paramsContainer: ParamsContainer, ) : Model() { @@ -54,7 +57,9 @@ internal class ConfirmResidencyModel @Inject constructor( private fun getPrimaryButtonConfig() = if (params.country.onrampAvailable) { ConfirmResidencyUM.ActionButtonConfig( - onClick = { params.onDismiss(params.country) }, + onClick = { + modelScope.launch { saveDefaultCountryUseCase.invoke(params.country) } + }, text = resourceReference(R.string.common_confirm), ) } else { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/DefaultSelectPaymentMethodComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/DefaultSelectPaymentMethodComponent.kt new file mode 100644 index 0000000000..75a8748835 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/DefaultSelectPaymentMethodComponent.kt @@ -0,0 +1,45 @@ +package com.tangem.features.onramp.paymentmethod + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodsBottomSheetConfig +import com.tangem.features.onramp.paymentmethod.ui.SelectPaymentMethodBottomSheet +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.collections.immutable.toImmutableList + +internal class DefaultSelectPaymentMethodComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted private val params: SelectPaymentMethodComponent.Params, +) : SelectPaymentMethodComponent, AppComponentContext by context { + + override fun dismiss() { + params.onDismiss() + } + + @Composable + override fun BottomSheet() { + val bottomSheetConfig = remember(key1 = this) { + TangemBottomSheetConfig( + isShow = true, + onDismissRequest = ::dismiss, + content = PaymentMethodsBottomSheetConfig( + selectedMethodId = params.selectedMethodId, + paymentMethods = params.paymentMethods.toImmutableList(), + ), + ) + } + SelectPaymentMethodBottomSheet(bottomSheetConfig) + } + + @AssistedFactory + interface Factory : SelectPaymentMethodComponent.Factory { + override fun create( + context: AppComponentContext, + params: SelectPaymentMethodComponent.Params, + ): DefaultSelectPaymentMethodComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/SelectPaymentMethodComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/SelectPaymentMethodComponent.kt new file mode 100644 index 0000000000..8f0090306a --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/SelectPaymentMethodComponent.kt @@ -0,0 +1,16 @@ +package com.tangem.features.onramp.paymentmethod + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodUM + +internal interface SelectPaymentMethodComponent : ComposableBottomSheetComponent { + + data class Params( + val selectedMethodId: String, + val paymentMethods: List, + 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/paymentmethod/di/SelectPaymentMethodComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/di/SelectPaymentMethodComponentModule.kt new file mode 100644 index 0000000000..48fe4dfaa7 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/di/SelectPaymentMethodComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.paymentmethod.di + +import com.tangem.features.onramp.paymentmethod.DefaultSelectPaymentMethodComponent +import com.tangem.features.onramp.paymentmethod.SelectPaymentMethodComponent +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 SelectPaymentMethodComponentModule { + + @Binds + @Singleton + fun bindSelectPaymentMethodComponentFactory( + factory: DefaultSelectPaymentMethodComponent.Factory, + ): SelectPaymentMethodComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/entity/PaymentMethodUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/entity/PaymentMethodUM.kt new file mode 100644 index 0000000000..08214001de --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/entity/PaymentMethodUM.kt @@ -0,0 +1,8 @@ +package com.tangem.features.onramp.paymentmethod.entity + +data class PaymentMethodUM( + val id: String, + val imageUrl: String, + val name: String, + val onSelect: () -> Unit, +) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/entity/PaymentMethodsBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/entity/PaymentMethodsBottomSheetConfig.kt new file mode 100644 index 0000000000..80aeb52757 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/entity/PaymentMethodsBottomSheetConfig.kt @@ -0,0 +1,9 @@ +package com.tangem.features.onramp.paymentmethod.entity + +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import kotlinx.collections.immutable.ImmutableList + +internal data class PaymentMethodsBottomSheetConfig( + val selectedMethodId: String, + val paymentMethods: ImmutableList, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/ui/SelectPaymentMethodBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/ui/SelectPaymentMethodBottomSheet.kt new file mode 100644 index 0000000000..75659d6baa --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/ui/SelectPaymentMethodBottomSheet.kt @@ -0,0 +1,118 @@ +package com.tangem.features.onramp.paymentmethod.ui + +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import coil.compose.AsyncImage +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +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.paymentmethod.entity.PaymentMethodUM +import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodsBottomSheetConfig +import kotlinx.collections.immutable.ImmutableList + +@Composable +internal fun SelectPaymentMethodBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + addBottomInsets = true, + containerColor = TangemTheme.colors.background.tertiary, + titleText = resourceReference(R.string.onramp_pay_with), + content = { contentConfig -> + SelectPaymentMethodBottomSheetContent( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .padding(top = TangemTheme.dimens.spacing12, bottom = TangemTheme.dimens.spacing24) + .fillMaxWidth() + .wrapContentHeight(), + selectedMethodId = contentConfig.selectedMethodId, + methods = contentConfig.paymentMethods, + ) + }, + ) +} + +@Composable +private fun SelectPaymentMethodBottomSheetContent( + selectedMethodId: String, + methods: ImmutableList, + modifier: Modifier = Modifier, +) { + LazyColumn( + modifier = modifier, + ) { + items( + items = methods, + key = { item -> item.id }, + contentType = { item -> item::class.java }, + itemContent = { item -> + val isSelected = remember(item, selectedMethodId) { + item.id == selectedMethodId + } + val itemModifier = if (isSelected) { + Modifier + .fillMaxWidth() + .border( + width = TangemTheme.dimens.size1, + color = TangemTheme.colors.text.accent, + shape = RoundedCornerShape(TangemTheme.dimens.radius16), + ) + .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) + .clickable(onClick = item.onSelect) + .padding( + start = TangemTheme.dimens.spacing12, + top = TangemTheme.dimens.spacing10, + bottom = TangemTheme.dimens.spacing10, + ) + } else { + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) + .clickable(onClick = item.onSelect) + .padding( + start = TangemTheme.dimens.spacing12, + top = TangemTheme.dimens.spacing10, + bottom = TangemTheme.dimens.spacing10, + ) + } + PaymentMethodItem( + modifier = itemModifier, + paymentMethod = item, + isSelected = isSelected, + ) + }, + ) + } +} + +@Composable +private fun PaymentMethodItem(paymentMethod: PaymentMethodUM, isSelected: Boolean, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + ) { + AsyncImage( + modifier = Modifier + .size(TangemTheme.dimens.size40), + model = paymentMethod.imageUrl, + contentDescription = null, + ) + Text( + text = paymentMethod.name, + style = TangemTheme.typography.subtitle2, + color = if (isSelected) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.secondary, + ) + } +} \ No newline at end of file From ae8f121f598a0a4a4f0dec7336687e4574990c04 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Nov 2024 20:24:27 +0200 Subject: [PATCH 53/99] Updated on 2026-08-14 --- .../impl/ui/components/SecurityScoreBlock.kt | 157 +++++++++--------- .../ui/components/SecurityScoreBottomSheet.kt | 5 +- .../ui/components/TokenMarketDetailsBody.kt | 12 +- 3 files changed, 87 insertions(+), 87 deletions(-) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt index db8023992c..468b7f7b59 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBlock.kt @@ -1,16 +1,16 @@ package com.tangem.features.markets.details.impl.ui.components import android.content.res.Configuration +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* 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.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.TextShimmer -import com.tangem.core.ui.components.block.information.InformationBlock import com.tangem.core.ui.components.text.TooltipText import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference @@ -23,86 +23,85 @@ import com.tangem.features.markets.impl.R @Composable internal fun SecurityScoreBlock(state: SecurityScoreUM, modifier: Modifier = Modifier) { - InformationBlock( - modifier = modifier, - title = { - Column( - modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing6), - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), - ) { - TooltipText( - text = resourceReference(R.string.markets_token_details_security_score), - onInfoClick = state.onInfoClick, - textStyle = TangemTheme.typography.subtitle2, - ) - - Text( - text = state.description.resolveReference(), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.tertiary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - }, - action = { - ScoreStarsBlock( - modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing6), - score = state.score, - scoreTextStyle = TangemTheme.typography.body1, - horizontalSpacing = TangemTheme.dimens.spacing8, + Row( + modifier = modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action) + .fillMaxWidth() + .heightIn(max = TangemTheme.dimens.size72) + .padding(all = TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + modifier = Modifier + .weight(1F) + .fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween, + ) { + TooltipText( + text = resourceReference(R.string.markets_token_details_security_score), + onInfoClick = state.onInfoClick, + textStyle = TangemTheme.typography.subtitle2, ) - }, - ) + + Text( + text = state.description.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + ScoreStarsBlock( + score = state.score, + scoreTextStyle = TangemTheme.typography.body1, + horizontalSpacing = TangemTheme.dimens.spacing8, + ) + } } @Composable -private fun SecurityScorePlaceHolder(modifier: Modifier = Modifier) { - InformationBlock( - modifier = modifier, - title = { - Column( - modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing6), - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), - ) { - TextShimmer( - modifier = Modifier.fillMaxWidth(), - style = TangemTheme.typography.subtitle2, - textSizeHeight = true, - ) - TextShimmer( - modifier = Modifier.fillMaxWidth(), - style = TangemTheme.typography.body2, - textSizeHeight = true, - ) - } - }, - action = { - Box( - modifier = Modifier.padding( - start = TangemTheme.dimens.spacing24, - bottom = TangemTheme.dimens.spacing6, - ), - contentAlignment = Alignment.CenterEnd, - ) { - TextShimmer( - modifier = Modifier.fillMaxWidth(), - style = TangemTheme.typography.body2, - textSizeHeight = true, - ) - RectangleShimmer( - modifier = Modifier - .height(TangemTheme.dimens.size16) - .fillMaxWidth(), - radius = TangemTheme.dimens.radius3, - ) - } - }, - ) +internal fun SecurityScoreBlockPlaceholder(modifier: Modifier = Modifier) { + Row( + modifier = modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.primary) + .fillMaxWidth() + .heightIn(max = TangemTheme.dimens.size72) + .padding(all = TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Column( + modifier = Modifier + .fillMaxWidth(fraction = 0.4f) + .padding(vertical = TangemTheme.dimens.spacing2) + .fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween, + ) { + TextShimmer( + modifier = Modifier.fillMaxWidth(), + style = TangemTheme.typography.subtitle2, + textSizeHeight = true, + ) + TextShimmer( + modifier = Modifier.fillMaxWidth(), + style = TangemTheme.typography.body2, + textSizeHeight = true, + ) + } + + TextShimmer( + modifier = Modifier.fillMaxWidth(fraction = 0.5f), + style = TangemTheme.typography.body2, + textSizeHeight = true, + ) + } } -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(widthDp = 328, showBackground = true) +@Preview(widthDp = 328, showBackground = true, locale = "ru") +@Preview(widthDp = 328, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun ContentPreview() { TangemThemePreview { @@ -116,14 +115,14 @@ private fun ContentPreview() { } } -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(widthDp = 328, showBackground = true) +@Preview(widthDp = 328, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun PreviewPlaceholder() { TangemThemePreview { PreviewShimmerContainer( shimmerContent = { - SecurityScorePlaceHolder( + SecurityScoreBlockPlaceholder( modifier = Modifier.fillMaxWidth(), ) }, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt index 6841aa66c0..07019d64e1 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt @@ -7,8 +7,8 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -41,7 +41,6 @@ internal fun SecurityScoreBottomSheet(config: TangemBottomSheetConfig) { TangemBottomSheet( config = config, - containerColor = TangemTheme.colors.background.secondary, addBottomInsets = false, title = { TangemBottomSheetTitle(title = it.title) }, content = { content -> diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt index 521ced4a30..0d04d56bbe 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/TokenMarketDetailsBody.kt @@ -116,7 +116,10 @@ internal fun LazyListScope.infoBlocksList(state: MarketsTokenDetailsUM.Informati } item(key = "listedOn") { - ListedOnBlock(state = state.listedOn, modifier = Modifier.blockPaddings()) + ListedOnBlock( + state = state.listedOn, + modifier = Modifier.blockPaddings(), + ) } if (state.links != null) { @@ -134,10 +137,9 @@ private fun LazyListScope.loadingInfoBlocks() { InsightsBlockPlaceholder(modifier = Modifier.blockPaddings()) } - // TODO second markets iteration - // item("securityScore-loading") { - // SecurityScorePlaceHolder(modifier = Modifier.blockPaddings()) - // } + item("securityScore-loading") { + SecurityScoreBlockPlaceholder(modifier = Modifier.blockPaddings()) + } item("metrics-loading") { MetricsBlockPlaceholder(modifier = Modifier.blockPaddings()) From 8a22b3a3195719680e47bccff479e8284f997713 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 21 Nov 2024 09:22:44 +0200 Subject: [PATCH 54/99] Updated on 2026-08-14 --- .../tap/di/domain/SettingsDomainModule.kt | 8 ----- .../local/preferences/PreferencesKeys.kt | 4 --- .../img_travala_banner_promo_background.webp | Bin 451514 -> 0 bytes ...img_travala_banner_promo_background_2.webp | Bin 419134 -> 0 bytes .../data/promo/DefaultPromoRepository.kt | 10 ------ .../DefaultPromoSettingsRepository.kt | 12 ------- .../ShouldShowTravalaPromoWalletUseCase.kt | 11 ------ .../repositories/PromoSettingsRepository.kt | 4 --- .../analytics/TokenSwapPromoAnalyticsEvent.kt | 1 - .../tokens/repository/PromoRepository.kt | 2 -- .../intents/WalletWarningsClickIntents.kt | 33 ------------------ 11 files changed, 85 deletions(-) delete mode 100644 core/ui/src/main/res/drawable/img_travala_banner_promo_background.webp delete mode 100644 core/ui/src/main/res/drawable/img_travala_banner_promo_background_2.webp delete mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowTravalaPromoWalletUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 8ec26eb230..9d42683053 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -133,14 +133,6 @@ internal object SettingsDomainModule { return ShouldShowRingPromoUseCase(promoSettingsRepository) } - @Provides - @Singleton - fun provideShouldShowTravalaPromoWalletUseCase( - promoSettingsRepository: PromoSettingsRepository, - ): ShouldShowTravalaPromoWalletUseCase { - return ShouldShowTravalaPromoWalletUseCase(promoSettingsRepository) - } - @Provides @Singleton fun provideShouldShowSwapPromoTokenUseCase( 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 9f4cf80fea..5fd333db8a 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 @@ -53,10 +53,6 @@ object PreferencesKeys { val LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY by lazy { stringPreferencesKey(name = "lastSwappedCryptoCurrency") } - val IS_WALLET_TRAVALA_PROMO_SHOWN_KEY by lazy { - booleanPreferencesKey(name = "isWalletTravalaPromoShown") - } - val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") } val WAS_TWINS_ONBOARDING_SHOWN by lazy { booleanPreferencesKey(name = "twinsOnboardingShown") } diff --git a/core/ui/src/main/res/drawable/img_travala_banner_promo_background.webp b/core/ui/src/main/res/drawable/img_travala_banner_promo_background.webp deleted file mode 100644 index bc3845b8b75a57c9212d9a0a7f5ff132302c4b77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 451514 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

PHApVzC4dxpxt$_WZAfUoDyLnYv8B5 zF7po(#}*sF3=H^hx*)J(hzXX}v&_E;J|(i>X&fPHRyNFFlf$#)+^8Dr@ECYtOdAlnL7sCE1?Se+2`As;QIaI<_fb1~wyvT_>D7U3JEyD?#1~$8fh=Av zbzk%y7W{VQmC2S~>HTo zXBwFh!K)3fO3*F{s{u?jr3Z>cT)TE11T*s#(bRPA@PM%kZ=eY3w^2 zO#|q%5lD{$Ga0&oi^8ll3V*(&p>C0txwBOZNGZiI-;8(7Rb zS|4tMv~re=*kmKf#I0Equno^<(3s@mdKNjL5^PH&5jiBsrLKMy$vjI391h%?q780f zMP5x2Y;RnxW_BGu3LJU8GOaP60c(I1$oq{Odhcl4?7hA3Bka91lx^>`9m$SE@wFR< z56-Um<|u}S`w*VW?urm0?h~RlSI-54Bp9f{*cUucJ<%UYsr#rOpJ&l;#%(It95)$` z@)!~d-UT`@Hq236DT25%CL}zUC4eJj&1~j=5Q0R7(}3uc;%6?hUl^`>&Q|NMP42nx zff*yO!B|z(AiaA`R!rJ+AW3F7-yk2!5w&V{yHIrfvl#Y^J>!^7h2?GKq*4i!FBY@a zzIRMli=PyTEiO`3;aRhzSB(pY}W zP8WHi7kQl*jKz7N>M&XrJ-0|hAxHowMT`*>POuZuOzFXvE#=3BR{}W@MJTkaMoFwr zK<&ta?ft-Gt=EYIcK?Es1;%r>g*>vhUSiaC9}RWhnFNMNa0XLaiR5Zo`C=p^49?*U z@zRzvBALNM%to98wg|CfIfJ(-P{qW)gf>=k7i21DrdJN@1w`p4L(f!A6C1gsKVLO_ zcg$fV?#8ig+iPA^)_2dO z-|ylFx#g%&ZaEh344}ClP0X*Pws!Kt7bvsJ4hOT-Pwhs!9_mU+f@Lko_G)s`L*&Q+ zjgXgjFNXr#@{ED#MQhvp=eDDU0wBsyS}hO}#Ia0;#SY@)j1?RlT;M#nHo-@vK#pM% z^N)6Bp`;*aE6|Xw)OVW>j*{61wDN?O1YS)-2E^Bk327RtgpA#$RVL(R?8;q7c(*}l zEUg)a0kKA|R1Cq!z$>sUa|PmIR|{AoT&;y#6tF@A;6y9Sxr}Iqa&+a{_`uH|z)`2c zkU+F1wFdaTfE$e&g~}kAh%}MODOmOOfRvTdk7I&j+?a}na8z{M?FteKHpi(5!%4p) z7dY1y0>=f8HCEL3-ZB0ixV9gz1y{Y5hf_y$l15cqMJBjdxyTg>;4t{mHrY*J=Y~EH zq7{m$EG=s<4C#Upi6EB(;3)|TZyPHg)Z}xws?{Xmo#PW7nZ5Q;R#7&gY(fnj@IO3i zAwyFtf@G2jfTCC&NtvKEyP?36a4l*-En_lvX0jLhOyo)<_5jn6O3=(p>3vZnwO&YM zAgnngE|VkhBB&3z380V^Su!-mI{Zzy`1{&A#PAcXA({wB4PT}nd5i} z*N9!V9oqL0U@Un7H?hHXAJ}>_c66eHCy;rXra;(6BTtyTSA^W4Mhm#GpZ*arth0maioU^h z0l|Bow9V%GY^5&Y&&b@(bDxZ(FxB@90ZiM|JZ1s#3X_$h1!e4*&c^KUVG9StLP#P| zVWl??dLkx(?f$W0Wtv0ByLffG(H_@#XN1pQJps#^GK_Z&9mI0eLw*nDB4YqsFrM)R z9t0LOk`1!Wh!V_(b2_k6F|H)y%3ugpfjzWr^F#v4>zKaI4Jr#`+!)f#Fl3y^4dU)1GO;whSz14rQn-wLg?(gqbj%}`+GSjZQ? zzcudiXqGsFx_1I_(b{>^Hc<;m1Ry|4b1qU*Q!I_84HTfl-DBIUKfZmN6gn@HX`43t$>i^hiQ zPa(yGG`t4N0g77d$3}h8uXwv0wO^T05$!d_I(lcLj>X*E^S5|~7II&|+sEW0ciPW* zmRT6l5i1$-gR>yUabh8EvJvU1&e)&4S`)l+?fV!r9X-G8ztxbqx)Z_SlXHiSnEQai7WuRQF6a&RgrQMwMx*43`PuVthyLp%Eui;p1#u+}p?lFH zOv5yeh0#Hrk%DZQ_W>XAY=lcyiHlP&%FvQq%B?}5*IHFG&w`U8tRhu%6ANcyTa{wK z#H;tFHYkSoF=cLwF4N}iB2qTjG>hlffSVg$arJt{YCu!C5yGPD?AeM_y&-1~A`e6w z)lnC`6>lTjoIsi6o)l)14@YOiDsZWVvua2UKmwO=Npu0=9ft;20V)KrjZ9EJL+Q@a z0lpo7Y)qk%akKKbE1eu!Nz4Fo*O-u35ljL%Rmus#BUS`+2wBwv!xqdWz#L`xdD4$s zOp)2W&`VnE0)4*d_P%|88p5Ih@A*fUlG%09f^wRn@zr=mhY6I0~AcNUX+Yrr2^7UGs=qX(F~mm26}f3 zDrY=Ft|kp4p-G$Yg^kHs3ii!GnR~Wa+u~dkzmuH+i-WSP&1b*aO=^pNmq5{xVNAFktW2! z0 zb|~K)q12D8p4pD@baoE1K8RHaX1F&s6BUw$s@G82i67Zipw zHnZ8~J}hkzF0Y6b%3^fTlSXqL?Ymwq3X}q!vn9-{p3YSr4!B?y` zTbfe7QznkAIWsjHFl}PUSOk&DNm{AP4qO432~d*AMB0ld7O}IVQj zII(H*P6O6HtChYQ0Qk$6X+FZVqfPq!SBN3#3$fG?$%rftb>rUJgvMYDBKaD!P;;)7 zRt3&FmrZ4Il;FdHbgJ{69s^ro*nmU^Q^1MUC`f!=rfQ_-WXxt1L1f44A(E7I%tND$ zbGhoetE-hO7Ow+Mg$#;zqaEu|&;3*Tv}E;bBnW8XbqT2jasf0o015z1GAMJ&Bk6lX zm#+=tTZ1AE2QXm*a5W;}VaRt!aso+=%>eI3dIn3uDo}HAc$ZF)stmH~+4S2iw(?T) zkQXaxD0VzbN0_f~O-@^uuO@P0fCQbY# z2^Xq#CYjf;cp-CHvW)LVJ``5k4yZWSHLcz0IRSbn2K@E#f|xgu3X&8fpHQ(i4^d2g zhFWVV&4j21W#P<+nhC;Ofg7*3rygFgSM~y6p1`Ifhp6^!Y?@S&wKPF2LE`7>&l|N* zSldma+l#!olO+S7-0CQqSf!@E9*y!^umE%^nYQ_YHLI#ixFdO_7Az^a|*tfcF|~r-9%RlX$Fei$j%;;S%5a^c_K67Mi$T_=kKBHrEqe#^u2zwKNbrux6-v zyyygQS(U4o_pM#`?r4vM6~Kb&`i+Ak&aHmb+XcpK4p#>*08lbh0rbOJY+LYbNE1j| zV_Ws#k^)Bx9W<~o2eMH_e$m5qGjCK45hIGjh2a+>GqT4`ivft(+}{8;2ARF^33nM63iuj6e-E6isz*C;Oe=_A%<) z{59gOHe*ZlDgctP003s-1%NY0-vZa4yJ;I1dR9jQZplGJEWxa|J|UMtpM_FkD!%lx z*qA_|uYG-P+pNZMBx7U)?oFkD4xlnfr64n=)w9}126nbZ%AM0C@sJF?CW&D1J<^>d zxT_)iyq6DQu_KKeJpkDH9@`UU`@Yaq&-l7jZT~d z^{}$rA4{!Tfwn8P!@iq6i6*7AH3-H7Yc+LGE`sQEC2v&0WQrJ*amZU@HgYOZg=x)A zSw5OBXmH+n-BR5tl;bPL7`jc6ns2Zu4a5=l3ui2qWMxw*WK!GKZ-69Rg*qZq#9E&f{3b`>wkSnNU-{iJ2?7M!qqObuAP_ z&5T$qTRt`(MFkPww%PyyL+|jBz$3|=na7IN|21w&7*34h3@i!&0Mil>!DLDW@960o zQXxR1SZM=D2H}hYQ?=Q)ZNKfwDqW3|7DFrhf`8>_DRhSNIC4UyDCfcgf{YA-N+efd zdz?B4PZ&$`cO5*OgIY^zO!iTQC1B3ypIDi?X*bEvLL`d9`Ve?+t&OCht1j9Z(dvF5 zJ&!{nX~)J%g70MAvix21jd3(FlLwv;w$!Lsw)t37foojTqJlPgY{)aTY{=t2@G~N< zY!oGgjt471blg|?eP@EqIf`*CX%GN(XA1C((sV@HM4R^^#C{9AGH}R#?FF&C=ZbLK zvjj0G-^XOA6;K8wr*uJKDrk#qNEuRX1#0+6c7A97S?$qQ3p#-32-PuoaVqa^HY%rG zw&|`@Cxv7#mqw?g|q+vk@AyFCLW=wVm=y@LaK03r~ywqc-mf2;;3TdOakO6R#($AhD$W=`0<(HLM@F1 zhoig=g5LJhXyaN(O=ATeNXK5h?%lEIa6CjmNnp%LuG40NE*I-+YSz_=w=DzuM})Ok z200xSz{_BR!8qo$y4}LCqZb8i!gNE#Kmds#919ktS+IN^q0j=#7MrtJ#$^AG(kh-=~OdC7rB%&myN)W6&Vhop@SO$#SyjYqSA_u`8}EnG@!}= zs2B*0$FV+)0q-F(3~b=~MZ}1hu;x!a0#hZZt{TitY=Rj9MRRJT zb8Xi!6*vTIao;T~W893R^+tl1Be=)|*AYpZj+Aw5$1Kmv1tB@_YOXj)W{ zXnKSE6crNc*t%5EJ@gMp!t z4LGwQBO{XI%x2CSEMLV|v{*_u=o2S#Y#*Ua&(e{Z6=u%jn#D@)DOl5J7ttCcOq+%s zyJNMUPCH07fGnLUw0oWTvUSopolE2O3XPN##&OHF zi3wOAFb#&We&CXmOJ7Mp)LAu~3w0$Mx*5Pkr_hXK`AQm zTCdD24k-qMFf9VJ=0ZD-q~_(75rVvNBd*5PPLuYk8EK``v^!bS78F6pk&-P@_UQGomYld1zy>!LB%NYwWDS1B@!( z#B>mp3B10G{Eoo|DHMH5A%3 zGp7M&I`*`!9M6YvAVDfr1HwZA5Q^~U2)ma;yf&(ch92x=gNjold#4vK)yKZKF|Rhc z+OWxRcXThIJhp=?kI+4g^rNUmEFdBTb`GoDxTQl(cz)H@+v72_dpU29ef8;m09REk zYcnjJ%>{8Qp?BHrTYBZ1jZm4v_ve?^Umz-6Ycg7+{Cx1_x+ANFytIPP~jfVF;hYAYjo(YP8lta*+N$ z&}BhUjZO+!i^nvTS*qJ;P@pQ`ryR7%h%1S|<{DeV)1HO{jwjZSu-JDry&ZK>*W^td z;BKbD)Tt{1R0E|>qF!g8I%->*poN&wjCt&D>Q7leM3)pg%WXIVoF7;R0I2|ZLY|RH zJT}Nd6TBc3oI8uy8=1~05;_+h-a;6n~;9mCRQ%|}9p!rhCZ%kZwxM=>?VXFx>YJwQO9=$#(n z@W~=(Nu^x(65CKYw2TCb*!Kc@6yJEyN1F}(7M5ao9w~BiO)3MA8EOYDBz;^aIs8Iz zAo&)|MiDgo4IXvPnlYPm`=f07u~p|*ZRg#wF)p9onFiPH%p`ht_heN>VSN}rJQrDO z2D9^7BSy4Z%?Fx$)ikxWb6-wbg$ph(tZT zu@q}*uf^MA0SV($GNv)5@DK%BwMOTCWXIq|Bw#uM!uJNUEfc9KtGk^!H}Q`uL1>n$ zz@;^{L*=QRCmW1ZZo*!#b{I$yGuTduja3@dD1A9LbWszTOJ{vf zA>UlP12lLb<__AppL>Ec#<$FjhX%xmC#%a-)j|t}O|tnBWv<~Q#~do}0NOFi zTnh7eBg>|bij3vJ7!g(@?iEBw1ofU!#|c5wH7O7o5DJtjSjqD3->XeOZnoRa*h@+P z4cH*3xnC@xMQCKhDhXY1?wVBvku)S+RwG^fSLo7hH9uZDXV!}EvVt$w9m2i+yKtE> zf;-NQDYW&R$z^vcDX4%nz=FD(nr*Hn&0OH5n$exZ_%akX6pSd7ulk8GiX>W#Hp945 zx^fse7KG7;wsnawLWN@sHfqh3BHUGwYy+ydv*2m6!O8H*=r2a+=3zpe>K}aL`eecq zO@lleX{b#Ojr9|O+wk?kMP=7$fQgGn6q^sqZd($C!z|%;C=e)R0gG+B*ejKoY@wMM zLJ62swR9Q}QhrRdq_L+&YeJl612#5+;fV0quI~l zuYe&#;ub?4IyxAU(x;7RW=^Y7Wv#ZTGF`{j=2OE1F&~n# zv6>%7ZJSG;W2_8i>zy{g3+H@PiS}FG&&Jhr99rq&JPL$jmOsb6GL$uuxKD+P?$tqx znF2FRkU$73Fal=}Gy0{~_`UVLv3$z$(n?VtCT!AiX7dVjOcQ3o^wYk@#T|c~j4>G8v{7UG`!k_}ZyL7*%vqPN3e+n*r?`hl8#1`Twe?KoEnHPOTHK}@(@2SX zy+&E%3ZtyKuwib~%Z}Az>MJTJx&CcfeP7qAO{Jn(M(UNe3Mt6C4`^7_fi_UaIAGq_ zj{%?=UCcz zG~S=y2-v~8GF+k}E0AF72v$Tc^;{L;Q(tyR#8Wlbi%vjO$6L!{aR*Wycuc{MZkn-m zN;){%9sv*oQd+!I>aJ!?dirY&!Pda`JZ$I|%W)|fA*;Q?EQLx`Kux)=sEkUG=&os} zQNIY~^+LvS7GsyDZ4SVpQ`vfZoo!XmYLt3*r5y(EeXfnQNlwH^EeB0<&Us%vx4gUM zebd2?!_pn>?%)4JN{wY0V9||Ju50~3AK{)2LoC!ryQ4+ts{S90k^}-Z@ET*CIb0Aw1(figX7$5s7Y)n&j468fPk2-0HC9QX(-ksdbB;Q z6i#Cr%ApmGgHjA&;SvCl(I?@eabS!lGAf-_3e;|sDiP?WW-SCHayKE+GQ2`uW^;F( z;c>UW|7@PY40l-_hHiI<`_>G=ZENn@y}C~sjdeW1&)AZvlt9@=kw6>nRvT$ZslN+I zkgGGnlu`=1f?bs;2vNIc+1r@}gfImK$4k-fHTS(aU$0^wY~{5n5`o{O&0MkW#5D4i zEHzCuq>@Fz_J+dpZgaIy-l8VvSu92x32PGx5nfwQ-l#M{DW!l_sjVxHVh0?fXlah4 zgFiY0Q(VzBeGtuNVj)tp9fW3A_PS{@a@|pW8^h;9wkVdK%*= zrFdW+vwUsY=g5xc6FD`QBzcwaE%imT3Q2*q((p+Z$Pzo#jVWf1kEm;9su<(v*PKgG zSHTkBHcS<>gKD@@t!f+g0FCuX31|c!+G%juZ z`ug|`1&9;?7;=zA4^4q>kN|^$Ex~Am)SW2~8DO|r4%bQ^Mv-#3I$U)M)>PEsM!boO zGYAw*N>Hd>T3b$a^4|)AN}#a7TTnf`pA`a!V#V`19J7Kffyckjuj5g`=Lf96Vk!p+ zqPsxy2+_NAj}FwmjOMrlIwh{lwG6x_y#VMPrLe%4Hm1Bv#iq|yE@nTn+Vsy25{kqF zFNNtMk3rJ}OvIH30IvW!vjdl|CoQtm`3X!^s_&&MxegAinKAfwDp-0eV>ba@;k$%` z!@4vYD!rd4H{~7Bz12MYB*a?IaEN?IXOm{uEaRb*7o=Q`e4!*JEOEAV;E>IbD%BGq zTvy>5>FS;w1VEM%5}6(4dI#bXI_onqQD)?T-9RgI&qF=JqA}?r;O8TYLJFEiT!(IV zv}3}YWya@YuhkyvxL9nS9z(X+LyaxAB? zVvA@Ur9^UHwFS9gq|5}^U6~s>rSH&|feguZ#WA^Rhas#(vIMA$hu7q+?bcQ`8iwGM zigKy~j|W&y$y%Bj(Y2B9?Q!To`bCQk{F*@KzAWY}bQw0159B(~Q34(T4&bA?vK=mb z7Pl>tKt{+UeHMQWDRIJADlg3%Pg_BjUB%ksh5(!v8Z&GO37t_4A*AoZ3WW06pp??) zdI4+A#e^MUvKddPRbdu*A-8;n!v^Ct#Rc%lji>?O(+1m30cjHeY;{mc9uZnCDYVH7 z4C!n(u6S(&tpQ}d!W%|Yi5vs7MD8?Mu3vxstk8^`!445E1I#n*?&oqF6aa{-b?|j$ zlqyNCBoQWivmn{ae(uQ4oTG4q$B6*i*la>wqJcyMCgG)y z(_R(uLduE}HUJ5#a3iqU9>r+yT0d?9&}l-y%u-wkoheU@dDVioM0>4+opA}9ty8IF zgHhT@aiB)itE?5s>&>KuS-a$&Ie>j+)Z!sg~$;CrfhD@ZFO zA@w{egb?eHq4_S^Z1)xXZXfA-=sa$qb6sdd$5J!ckn+P`UAHb_*ak2J@=WGIgZ3nm z%bbqW(Sl>m>tO{C1mdvh7;g&hHQ7kRPVh2<)FL_oO+AM;U%NxC-? z0FV-P!8l^bm`(c`6um#DO#Jyc4QltQn&2&Z`gP(#I4)fj_;m|Cc{ch|U@~0qU|Phz zY$t7>V{1+)$yi+$?9M2|u_ZnhE1gqcZ|#KD_&WtG1Qwq(g~+F|(#SH=3TxJAWatKS zP15E3+aC3<9|~B%k80!#z`sJi^X6_F>H70#zmtTO3mzdT-%K$L__maH7^UEtUowuH z-$6tHIc`@C2``tW@%z##E5zIt>l(WLU#6f{^8w+r3=xxTpbPvwwee{^^%pBOt8gmL^txJN(246S?h-e&6XGi6>BnFRW60a zlTF4cV3^+luhpQ^{?`8avwidfaZDPnhV<$93UUSM9rh6c03dm2IS|qTh^Br?5Oj?~ z7E6LuJoQ+g1v!R~oaMWxG>F5G6K7`%6YScMBq|YCya!!yZafAA$l;NSN0Q5H|utrxvvdQt>H(gW^q%PShdVd;SZof)J`tz_vX@WBdG7~ky|k`A$>Fk3|k2!s(U0oSe| zm;f!#8XBzPGTm!dKW0WziA1c^smoiLH-SDHM0~fb@R$>&#En$!1m+~BOhy7rZ9mgAHtj;2+jcJek0uyA8}g=?ooz_>PteHXCfJm$K0 zb-s(wj_Z_}yPKHr8@#>)*Qo6U+ks!E%Q|e=WX(@X1u2m&!wOuP3m_d5NSSFVu=Jf!<|Rib_EyaS z*dMZh31~9H%0dTR?D2=dbT)#B$Blg@}(i@&Y2wGAiVzqT?hlCp^ z7zla?1 z@{?=^_jr#DsXauMG8-CmnZMvvvBuQAIXjayPD-imIf!wMU;*{uUZRS(4**u_XBTQfHD z^WiuXw+FxtCjhln9iX6bv_%k|m^Mfy`Y(aw>TbEEy4pw-6@U@*0oW{ofCa2^R>AXko5u|P$NbMJ&Bx70+{%N~FJT>>8xNaaU@GQQ$1+r#qtQ z%-q;nvVfG|wXg|djvay?D7l+*u}G{9kKpgXx)EdUsIt`MDw%gw(AM3PyC>}ETx9~I ziKgOJetwY#H|hy-;le7w_e>Nc1rI=~fes8LfaiM`AT7BVfNKF25S$<=;Ivv*4_fKTqHnnhGGIC4BB4x< zgeZkGG&KzapyEt|IVP31kOZqL{ODkriH{ZY0 zN8gtHG4?nB1~hsLYD9zAaD$Vb z0#!T>YTd5Q8tw%!bfsdV+;j$vzxzCgY^XzKW^rAoa+>A**)G2G>^3#V=IZ>gW$Cge zYkrcAWI~A@v=?w??c{VeNMsUuOcENm6JMf?Q2|kjdn2rIb0Ywaf^PYQ zaFCsotgOw_BqSCYrPHLXu032m&)w^h+rR)ao&zzal&JAKni0aDs93d53K7>pF35o} zmjpxtV3O7q9x=Y*;*adDzxaH0KA*o@nZi^&&*k>8UXO)Lz*|#L!vq;}e-83K?xO_( zxk_wM)yAQBoeEK&q;eXY*1)ssGsbb;j`tx8v|l4DHkgT-@Q=Q{GL~_>wD+lpkK>sip@`pcHpo;iWG9}wnboJZgshm_h<_RROIxMcuyY6(lWE{u$y5BK!ArDI2pte#;_zfaUicqr^G&;4yXaw zouiJzXe?Van3<2eOaxx%nH3TbtR%2(&$&NA!zMqVVNQ}zGg6#fnsJlJC}L3JYAlXg zXxZy|D5yb`!VWQ88rG%<4^6YK@9I2-`ahY^H^Np_W%xzum~+d^oWND z%kWrTfdl5|p^b%W@*2`Ldw5ns0nEMc`;5zREw?pV45j2d5;Q$V3!Kf8iV?9&jf}ux zLc{QwQ-^P`sxTS3j0p_cvV~cjNwBMM*WygbqTnaAEhV2&fx9o)>pY4AFvn(l%RVIF z7E%=)rzNLzuC(wbahh_P&d65Qj6j)&&fuNtprc|Kbn@H+bxD*V4poM*&$oN%h+5sl ztz;>WuQx((No?N#KOpihLmwuc9Y<0+yI^EO<~Lj@3j^~&tJR)`AD{qeNTXSJ6sAnz zzA>DO*iaK`#j0&VG1C?wWSYTP8_8sl z0)Gok!9tH;Jl@U_kV*l|Z@-?)kW0&#?ZMBuR$A}eVYF^~ljLt-(2gH~v9^5Xk zXyB-}378g;vU#c;DXTwcHJ-XU@_}N(paZDb5x9y0ovVjuNmSRNP=vrLkk9jcTJ`{2 z-lW7~R&oyjC@nVNWmBP7!&M~DWQN>19xZ$hiZs}48yxg!;a~^uh-?e9DAm-@I>>Wcs=$ka|ZK!14*OZV02c z?%uP_u@q6UPMa#%gfh1yftV|-1!H|UdSu|IY1$It6uCiGDOVc8oZ~?Yh&-WF2|xix z>@pkJL`T~$U0Z^1G&-y89D(i*jpnXqcug<%KvMGJ$cx>jyZs>zi6R^7A^kpiSIpGN zx_fQtoAb~E)oY_gq4Z3l9CwL&5Ni=gu$-s5DJTIv<92jfP&FpnrhrIN?eMvpK~Y#k zK^MQfq7XqzyvV-u)^vRGlLy}MBr=++!7T_WCmPtSMtlLjoR>06bp(f|aGjW^g6bAt zah$U@#G}`BD68ZcmL*Bc$HVpT?5lt3kM_>*eIEKXUvK^T=YwB$Z~WL^36Jv-7)2l+ z_P!69^IM-UexJYn=D)xBYx_qT#QG7##kCT~fqtJOT=MGA2U7ceNYTT?)YXe(0aetu zf!2Tmt1uU4%{GLM_Y;n;&#jG8!re+VI zvLr1~O-Ql6pTRnK6W&ahoLPLo;I3WMOM3U)-?-4KQUS@rkQ1oTblD71=>7(n-DoWD8l_~3i5l36lwvX!)F;ZM z0EUdpXD?9L#BusXl#;*#603~r0iGn-`S*|3_&dR}-R&f2=3(@4`0Jx+b0SkZ&Cjd{UxSCi;D8W+#s-Z?4jv(0xzysH^ z?|7t4H3xf)x`F$cR#^$L35CAz%RL7_@I#{AG!7S^t;Mxl1*yS+w=T}|QvODG{&_h^ z_V^KB-N849(=1#oVA|hfgN<46|FatOU==B(%21r%D^a4I!E%Ok?1-eWfX}X`-g^}Q z|I*?F@Zv3MrKU?9cA>>il9Q#e_u3V>GSggow1vUy;?De{x#&jDSavSk@JkRWTAxUW zSB_`Tme(+(!C@#*Vq-`I9vTy6!mvt=@a0^C$s}YFhZ!8W_8(l^4_*0t9QQ9!OgDL> zKs#(*ZGB^FdmQl}_tM(zodX$hU@(!H^x+}y(OQZ5{s$)};`xVje$L`wi%~JfZ}T2~ z`>y-;|NYq$yz^OYZJNL0Zm~PMdv)Kddq48~k^AWOI>$JgAtHbo(xCB*QPK$vK4fBG z4})@X&Pv59*26?k=FmlDK6m%1!Xn&sz{N*^Hza0 zEklv7Gt-a^JM(B`=$j^Ql_FMcxcGLO;5B9Q<;ds1jElfpkWQL3h=L?Fgt;ykj{~}#d+la zYXMr^ShYH9%%+__zi>}s(6+5|sdG0v@4&{w@t6(IIjt2By2IqU_{0@rgc-&Qp*to$ zgEwOG(0Rb8h|61HI_d4kcR2?ysD`hkW)@-0@gzO1XJZK(DAvv@b|L-1%_T0l0GeRx zo2e*uJsG^`i+N{E|Juu?fB8w?>0f}i@R9L$_)g%h!AJUU(x0hehF|%!+PVx*GzK}g z7ac|*(i`Ta)&OIb#=KJ+bs6#O$?J>czCn8 zML~KiP2Xfvq$yub$byh^$cW%FXE=zrD!^t*07^LP?peZ!^SnuGb5#mrzx>O90IFb; zL68W6{Y?fo4f4_bD&wH%iV59Sn!JSPecbJ%I+)YT`{JE`jlAU_n?!zCc)QR9J* z*VcuER^`Cf4hHXDMlO|S~wy^EXS5I1LcS1A?ydsy{U6;#i>kaF~7IR`> z5fRFk9L4xPzo8GeiTB7rjEncl4jVj!_i<}Z%iob#tYYtx-&fbGe_pTtSzX5-!!Y;? zh=#yEBpn!P5@%2wBBC7y=BjOFB}Swwdxmotwe)tx(`7e3ba0DK9bvJQeB|IjbFqS% zoEau+Nu3|KOJZmChfamq?|Xq^gZ1C;d|t(L=%k!>LQ#QaOGIl)m?Ui*aE?a6u5;s< z8<}&IArf29%gBysyRa?n^8OAXVN}z}+YVLRUZ=SLaJCFRMTW`%;oK`vhf1i@L}-{~ z|K~t59)#-;?G37kUIlS(wNvLP-0w=m@0N%OC%~}TfMbNxtYJ6^g!nXQ;xy)vu)KGP zQFrs={y^Kl2+{#8W>y5vKebS-LD-YxTIKXw*B5TBBIC3dY(PcM`vzdxJkpCSrK1#e z*4?zqF}3&Y(rl^s#XQj;LDE>jIo7U~xv&w%@;zN+6!>l9rcMYj4QyQOqdVAbI|?4G zu#rf=%aSQfge=@Y7`k=Ego&~xGdDFiCy4iVJnw?;PxoOuJ=8-yE~d6{_Jh{z3uN->Tkhd(gF~$_PJ`TFm&9(!-Xk3^y1o2lOj8+WGb5{ETLhO!>&#x zr|iW&9Ta=67i6r1U?Aq=&;toJL`eUA!M}fzMo{rpMU9w=ZNUJ$1h^uSk;6{Pj`gsQ zJP9g!>){Q0I+4NXLfv$}^NPsJNHLI(Oi@OJlqPmurT$c)l5SWQ8 zBe*Sn9!3Msb8S0lC8D6|Fsp40rjppB#1QziXB^mwBmfTtu7)HT{e;5^V#BL3u_HkS zXmyW()&`nD00;pNE@0X?$8&ZmMe+o9e#-Ysp4>dTCx5+2(LB@H+UML_{*G=(*U|OL z`|9?}du^W4bq1s(j>W^nVF3ad1$-1M#PKkmwtyAkf=)N9pCy#Isi~ro1GidKD6dU% zB}|bRsK>m|lVQI_=ls2Qbjqkhh_E+_4&~tHBbC!Z9IoiVqdl+|LhDV>q+s51jMoB} zUDEP0MzBLRwVFJ-V-Klb2Zxf47vQw@BRoCgw-L6xaI<#=a!$?04-0H7qs`PQ2&m)l zGZK=BYCDfWT;b!(YVd+` z(N@hcp><&Wq_`ua7a20POCk z3*J}?rXTOHdaBZ}t~(gm-dfd?%G|1pOVnD_D;-&*#YT+pvTq^Rsu;%%14bfXONrG| z?+tYg57*J?Q|_G_SBYoO)oa32KnjH^k}Mb^0GBlrcZIqroxpgffa|8&CX?Qnn>1_Q zacI8y{}Z`vZ-%gFn9IiA9Bu{$_r_SHe4? z^8b4~RECbZ1b@z__^tTvz5(!2ww^NDlytTk;6O-4gv&+PSk)H^=Pk}d4QT{zzy#d@ zd9~7Qj~9%YPD+Dn1B6Wor-R7pH64f%@400dDIo%)c=3XQ^s5E}9?m&Nrot*ZB7q4R zi~*VyX-LAZ1KMK=bCU_W4Eyq{(#2#*{w0stq{EiM{!#}A23vWs7lWqnY1z5}EI~+s z`Eo3wSm{gOBRFy6hyzx#&Qbms0I8?&(9Th-B)bA5K}bT-gdFe$06<^>Xid=i>Ct48 zqOzO;2fzhBPfXN-%L*qVLimUhRTts~!8cOU!jvHzrV z`|_|@FT|^ZB;xl4f+k)IxndTYQnX|_TmxS;@Euz}p066BNS6Kys# z?Hy{U!i^TL-4^E1ZLtPKi)T~fSzfX>sgZSK9H~2F!a}9yRi&}=At8h?_hzF% zy8`LFC)@Kh4_}gbt1-s1YDNY=Lg0>e(J9xTk`{9kB1=?^RU!r+iso`(*wByyx^ZP% zG2@?ahz(_*SB9ZKS9U=6zOnEW2wK`fAGEm#E9F9&+yOm|&cT`4DlafN@t&kD2)5-TYBL_eGgn{sp7*~PzRK_ZyHEcS9PE73UCv;zkVpV4aezA5&~X6Uz^=VRD7BIzjlX_tsAbqEfDTui8Ca&7 zO%(WGtML9Hlzc+L-H8uD%5nPD1WE>|1P3JGo#EW7rzzdXbjqR1cP^!H&Ld$s1&vo8 zJMUd2S#7z<=$4OcXm+`jn&y(mUNnE+ZId55*H{Z?@*oavO8p&paC#?7^2?!EDM};G z@)2PW%OSl?7BoXqC4ebzdE`0hEQbX!!jZLt-LlFuFjLDjAQ?{+F(&Xy4oiTTyVoj+ z}yub9eGE$SMIg>vX>oq=$t?Y6$4R3tV1G`fX1_^ zD1<)d?M-3c97Z~XR?at}2qLPBxoM)=wu7Z5DC2gHclZ0Bm2#i>I!;iBXmr2=6ST3qzuEMSd6SJ(PlYvU_@lhmUPqK01t6s8m8bJtIOVZJYX)X znLGCufWPjq<46mR{?gIkJFZOin#!2=_x`<;em1;7u7?9ZItKx?#1etZUc!ova5xNk zXqB1#-FPImvyqNjHVleF*C;`plx2)rFR2E5e<0IW*BXaDh? zsR^l=+`Y*XzhowUCt=&q5)kt3fkZv|TY&&P9M; zRB#1zl@U4sZXv(`Wt_p1{NdcIw2d;oUS~ML!>vgORClQUBzioD0VcSm37M%LtbsPE zqE#Sk(0L~eDj^4;4@cyPEBbIm#PfL~x>C<*&-+YQ@xvL3-)3jPzGh!}yjA|TE3fR& z``VLt9CPUK1+xplseoS$2{d0IgS}!a6(qKh=%diXd^~lwgB`ZE$6(PDRfRgrCL`yJ zmR(yg!>Zf(cTSVW0f}jl9Lg;>6GNpe<)nMJ7BdP9hby#jBtuTeAOwmag!Y&bf|2l! zYv5^1fgv*z6ax3$ARuweC*gWp^5Xo}c2h*T@l=r50Q`nlM@+`Sg$<_RdMYGa&5v#z zT`#xYdLqOOv@!Y!Vo!#u9wNfuj%ReOUjZT;=Z0}X*M~MTh@gmiH6jHYlA#F~o^C9K z?PIV@isWZo{|NhQCzU@=uW8+c^T$erLj4L)rS6NmHEDl9p70BB#sl1-$L^Cq$qGKeNf=%XIsgd*5KPRmXP#HT}qo-El7W~YIeH-NA4Ju8tS=Oj1?mRk2 z$#kd7#*Sr!9C_~0bFjo@`a4!?VlLAznxb@EO{9aahm;EAEm5D58qZ@*v2o`*dtAjb zm&hDH2U`JisR~l4Cp9#5*#U43b?2O8b3Ji3Wqn>3Fcov*(o5g`yYsF`{^*_Xtx3NC z;^2>h$RJ@iplCS(Xt6*}F9&3^h8I#90f^%7YPK#80O%SdrZSzfRVj!Fa(jzR?|J|) z-a8C>MSngdd#a%1HLxHfZMaym_+ zzoO7YF*#Dpo%Co>F=)!&LRuhHh%6LRJ8H>q)6jc~PD;aIVvj^SYIchEw1+xt8>27b zUZ1s`?+FkPCR1=@6fR>f-~8vud}cns(-Bn{0JnxXAUv2nX?ZOWZ*tW7d5S3!#k5^K z(*EM{WgTrpD7+$yHA0^!sj4M84DqqeH^x`qfsWY{G_M^+CQ z8cG2Zr=viQGN#%=lLWxw{7wMI)56ME|3komkD^_3f$BEZK&k7Ad+xbsHTT?q%TahT z{yOn?4E~8p~$u@caX6 zzm*UK%H2m?94{pZ9TJ#;k!x(y4{^+kTSbvlyh-uq!)Ak%ySf|lySv#hjAtOr*rF5% zwTB%%Dmdc&FaPNx7IM?qPRaljUrY6AR5Fa9LZ~lwRF)qu`|g4SWgknCv;dMW)>N^T z7;QLkaBYDxWuy_s)Uy;AfYMVJMpc|a5xM#Nea86eyBmWZr&(iHe#@alFWa-Hd%n8bsV7_p z4I#FHAp0mnQy>9}#6sj*zzzYZhtuxdWZ1e(Dbk8Wu^3PucZoI0pfm#*m2kg6kuElk z8IuHCTq*N+lAyIW;nsFebWf$@t!uLtmBWbuH=ZjKJEW{zZZtzlJ04GsE=Z)@c07jW z)e?DmNKz-!4nhk@s@Ff#2F8K=Nk>`!_7NkMs=kO6wloGb^W3Aa#*_PhJIOV6o;!(w zw6tCrL6b4*>s7~8NXGAh<43=lsY4Jq4w~U8S`7TKh|p+u!i#{Y+z0|a;~}Wr(kLrM zyLBc5kr5co21z-)uFG4}j=z0!m3GSY#9@_iW(Te@vnpA2j7<8aiM;)&etjQfEm>s` z+0J$xXrp;4awX4ZRYE4Zd^RGq*t`qF16Ykilgyc;YAKON9emB@nm8IJ%maz3JEmHx zB7&O&YrIS@0HCVtdO*rz;Nm>iQXrvZ9a2OlPG&0ywFz%&R%#qeX;)f=bmtE%W92aWua+!L~QHv&CS-(U$3SPqsk_6_GHc$%8um*K9;b)6=u%G1~)zmL7W#7KP2PM>szC@|T zCfS4PJP42(q0QkkiA1P;?(VWo3HMT=)dev@N`$rrhG1;CP7(Q3?%uZs(kU%Ay@>OE zZUb>89q69bLxz^LWPJ{(L_~m9pZ@!tkBUhz^9N0?^mITO^6D9e2o&i7=>ij?IDe9O z%Re|#oIB!eB<8eOL~<^6@$(j+@s$wQKm)Cs7vm;ew9|LCx=xE&FA~CI`EUGA2&_p`$ zkW4(;5M{|`)?7^75bFjHZtE-Rm)I{PDTK_LMg=g0kxuo@+2tIYxzUa+wij6r9Jm{s zix=7UCT%+(E@1O7`{d68YANLhRGlLFPTvl1fm;S!#R4eMJNfG5dG0_R4+4w`FAiKa zZ;k+puXHjbL6=&*QOihxR2xY3`WB425kHQrvc}UrmD9dp+JOhSWf<^+0w5NOAXhcm zI)lKN1mL~1R>T6p`_e`0Xpr3v<`M^q!^hH)l8A#MEl(K-Gh!ly_&ydQQ5Qr7Ra8*} z5X~=KP=b2bo!hbgT2|eVi;vd^=qNb(e9Py`r%ttdEaTaqZ%QMasH=Yd33s>1l)Q5fog{G;hG7w~iX#;}h&f=Z zU8j**I{zj`CQPspI?50Q)17wu>K=_PivU*|*VOk|BdPj^S=wzAq>L*#Q&c9@r4HfD z@pLU-BOX@q-h`(L+4uD3&}sI$WUIq0!32C zV-NmLu)1R1J_et90#_;kDL4cW#Z`Po%7rl0nZV0V2~-)Y4gh`f6PJ^Rb)p56 zKL^YfGh^Lj9Z2){U*MO{lOJQe_c=3`T18QWa#iPaXF%N!CcsdTDL7y zQA@BL1qpG@Dugyj%pzH|^vniqu)nJq^QH!`p|1N%q`LvumbxCi;Ia?7h4;K4c;!#| zYr`+j_c=g3MC+>XdX-Q_i0D8-za33yur5NaZnO`W>_&jWcB(>cn2?Fhs^matIimo?JcgJ29?a< zGVvHC{8y4SWSkQOP(*aDhF@zfuEqKhA%JN5au=lW{XY|;S4B!@5okeSp@bGdmj(zv*vC+A$GKv5VB1@a|oALnZb$tL2s2D$emYr=1YZ1**JUOiiJg z=fDuQ!Q(<<8)Rsqry<)uo(QmBF}o3R6>;7U%K&9Coj6RKb0d#woWN)#+DhMHvqezP z6b4+k+R{taU}*{Oo;2ccm`001*$*Ws+u@O+lli7-=_M!k_y^jhsT2}NqFg&6HGZgF z*ryuCG6y|0837(hc!g(FUAsXRS%3}(b0eLKqS$A3?G2nL0c{Va3%y}NDH@M<0)X*6 z6F~r2i&e^_%?svc(NR$6dv&9xrO_c@siuJ!hVXL@I-N&@6n1u+5GaChi?AE3b)oC8 zS2x0oAmfm=x&Nk>JMC$cdn;a{M~GT2HtI+jkG-7whoaH}9cOE9~w zrxz?K$(wu#9C%6GvVCLjCqfa$LbF#o)8&{bGB_x0S>*a(~ja6pYb9oSqi zDs?fJ&2ZEwyyvCxYjX^KeK@wP?xSw$G(07R#Dm-Ys<#3_u5eJ15-etVMcLFQc2T7q#%SqjJe{~jbNg~ zEC~+6&Su0}oDUp7m*Qa~aJ!{HEdgC0o;G%Lc5QnIk0)jDghdh}hvCjkA9$20xr+(_ z7fX>y3vQ?y2D2Evjm&(JqX<*a9)t?|AJ1Uu&kqr_{MvXV)mb75@aLep*0v7xIr4@O zqg)%F^7LF2!K%&EwIRaNH?6b0#>Vu6khu2%MnJj0%*3?y_F@DusoizXpF|j`SyHDi>|st7hL$``!4 zk?GET>2}ZV)|S%E(oL?P>3|tGpVjWoB_!heATX)Pwpr_Q?C+SdvlBhtwm6*2tX&;6 zd*D(7+tfYWs&ToV9uZ@7Fk~*5p`4gH)zzp(Dom8D2@<@D<)N#a2pXjoN`_y$O+h%( z0fg8Dz|-xe^ikm~_YRdK+h13Q4FMjFbgRLJ=fMYo54q*2PYf@Esn#F#1OAu1B^zZ| zHk4pxUex0C=jPgQVzZzIGNT2o@p`B!c6{*E#P6|lcm&=kpjEBIqzgqAF>ZdkYVkeN zJ|XqM7r-71SPEV|S2SV~DioGT3e12LZ@7#BSU*L9(!0#t-EW1-gF8q;BsZ|E!nfw| z^tpiph9FcY^-%uxy560gTr&WhOzcM3}Olz^UXhg%=4XyF+x zs83LXD}zv_dW|X$yyaQ&Q8I9u#yZ0YvDTp|0Dw%m9PWdAViKl>S3n+!29uxPCuZI- z)0_-ENijL{MDfGL&Y1U!cdYiwC3lN-U%>Edb_G_8|FMgWpGr1Sh# z^fYt`6^FjJ0b(@_CKB10Up7WXY2w+?;=F;;qS;K$mT=Nm)@F#ncPPF?i6uBl_ayxf zG)C{WQ?j0_GGKThv*0OPeh6E4?D`uKj1h*{s6>AH)AHeFVSTujR8*5#&&wD2K_Zk} z4HrS%rjt#vNw0@Gi1SDSAqcMc*LqefMTHBcmiM@-Y0H9fA)%4X&3$ZO#XcvlNoyTh zwDKOj^_qes333>Y>Zv>#f`VTDz-X2Qy#vLFhz3OCnv^H?=+vdC(g8q&mbn4#$P*wJ zoVJ_mjlsZ$=FY0(^Y;E$d0SUs)Kf*mp5%aa>#2NBrqU->s>Bjt=8aV?OaFMQz5n5d zsrgn2h`Q0li0HC?h$VRgT5!3^_r@;eIo8@AR2Vd^(Jp3tk&)abof-N}*&p4Gsf*!@ z>h~AP z60huzRx!aPrtprv4zP9WpFUsjuw$4vi>tZ!?yA%0r?e@Eu4i_t*2d1x!eELVH%^5a zz$5NG8Y-ZKcNg3;Ye7xh5R*uxe%nStpT^R!WZToMSQ&!!s3#y{1PPvGq*ob){5h(* z1R=GPlR%6j(ycO7k#F=9GVp9t>kLTF+c^15zrpfTeVGh#8^8ArygEzSXzzlrb01AV3sH zY|uqlrbTBG&huo{*67eQaBgSuhKf-*eKyBvB8gdDf6gs^N#!m@xjV#ZNyX$WU4fTz ziRgl6@B}SU1qkqW{KaW8H&n`qY<0)Ww_*|X=QcKyrb&%s>FVrE8#&xPo}`d64S-iF zBy_lbr5l0Y@P4VrMt(RN%z`^YJd zB~ez`CV_3n#1@xsCZekF`=cdxf}w6+n%|--<+|W$bfY|d>BX%k#n7!Tm4jZJf5OQH z*=^upwU`a49YjkmVW)^%kjxAp+|ZR?iKQlI=Kn{92CW4iPOaPJlEXGxF6R_&931sJ zK|VJ}a)VK-04B}W9S%@nOe@!#Or|QKPf*qrn0KRk3g@6OO{X0*PWly{@xmH3fncZr zM#)Sgfwrc$j#dm0y4QfSO;iljD;{-vwyT z*h^MOL^3-DR8g%IygypyrD>y4(0qiPN{a6>=>y~O7zl(b8a&EMGEq&jUnG#2=Pfpz zQ_0T+*8`;kQHDtJ`?8y2TTQS}7IjH}uC*7xEAL1aY|wbIC}9Lu z+_qKC<=4`FImD(9NK5Ha+IRwFIblfN$fydy$W`3Pj!7&C5R;pEf+lh}iDzA7yA|6# zQEEuoftv^SO}rx%oItmXXZA!ik8?+nKb(;`|8Oxma;|e~Cc1|0vJG3ar#a2l9(hh} z&RQ%C5>r$15G6Rk5U=Y|7#AU$LoQ-54EkVm;Z2FsG~(J7gE7gqB7eznMLC%m=wXIn z=H!+!>3(8sY=YrYZD8YNcGnR=|Fb{z@S!J4FPUo;Zb zZxY*j)0Et#nG2c?gmscIENd}JAS@HU#qH=gt4BE^+qhbE#w1C!@W#+q(e4yg*@i;VV_X-VhlmSL+6Rt!M#)fD8o*GGsFT zZ=o9b@|Id1JK;n29E-MClyr{wh^#mD4Cv-trL2#+s?RgrbA*pefkCQkxpHEq)tz)M z^)bonWq3dOhx6if!#MyzMJH&}75YtMgy?D>F-t7~0Bq#!=nP#^w+(|(s@+`UMPLQo z@^^t=FW7QCFAIX-WVX4gtH~k}T5+jOLS-98ckiN<#27;a_{9VOhsC;v^o96`FP%#; zU{a{G&!qvbaxp{!J_0%{Q;aiKPcYMXoRJnHT=B99>7pk+^63|NzP27J z(8Dnds9;pQ22WKU5`YYDbb%(o6eZw}a=Cor~bCZT@T!m1tvy@yBOQYjRG2)f71OyH8 zC*xqUC5wg`yC1gD6{BCk)9**X~~by zxkyJt4jZ$Np_KB#j*|~CkXKexvGdW^Ec|FXO)Q85Gn3u$3Uy&+sh7I7eCqB)&9>kb zMw)nY^yKb?)4O>+%^GSfqgUU@vGK~9+W;D25Z4J=3rjfm{HJ`rC$3NEO`Z^#qwh-* zX$WQjVLG_bEiln9c(fP*RqB$|^>FKm|U zwv@N-o3U5w5?@z~!tc59J-G$g4D6ZX8Lp5;LE1;Kfe0$P~ zd*>5@k|jI|O<%2=+jK_uctHjhx;Qb34ux7bXl0$RR!EgP?7;%d#H>&m)({EL#fK3ciV1^U8q^0=@pt@Meayb>AWfl_PmZ}4&UOQqCNuaU~@54qqOM{RIIS+wi zLh$UoJGEhB$bgAldO89GREPK+ds>uEiTK0K=R_2VoFnJRNTO(9;i7w=pD%`2^@k<5 z;ksM{W2be)ccPCas06JAj(x_u9&y01>)OD+g;;Q3^iPUqV~J=X#wn|oPI)V|4aMU$ zXNJ0w_1j6nE>n2tsvo@^C+|E>C$sbLlU;|pu>3RaAyeP1h%g)b@Vxwu0qx0DDrr9w zCFXX``JIw8IFi~yWH7FGoB-0?R9ifLK|uL0|TL}g9+UN6RzG~A-~%Sof2?= z)DSku=*t7i#lrI@-MrcdakLa)gi9*z|wdRES*-KH^#|} zdYzKaIZnf|lS9f_LkiYPF^m18k2P;twvmtpkI{4{Bf3$Vp{x3Wq-iUlAZP$3R6bm6 zUd?+Nf~Z{01Tkq`(lc6w&+-ItHwm#cqmhfZa$lyt+L6^;!cj$iE|R*7WRmIyc!#S9 z6(BGJN56BTQ#4Z>TOG{PXv|~1Fbh&bi_eOP;EoTy<4(G0qgbwG1g?D$QZ+cAcvRMG zXEG{8UUMT1p;sYrth(Kstgi3e`hf}MnyUaZ>>fwh1aF!{4cP z+|m3=1S0y19o~5t+vN<`wbrwm(@s6p6}Aba@hI1kgo01ZE9;Qba|jQEqd40waM?D} z;AFWm##5o>)GU?b7T+*4X4WXWZst@;f;yRi8(s*YZ=JTr;CQGOkK$!Wa-;u=m07CDIuhd~LA=tAM_6F^0SuW;lHwWhs$q}Iv zIWbFz3x0wM0!vmH0u^cYPW(oZ2Cg)U{gaSGb?Jew8`v7taje|w*ZL$7#W@Iiu08Oq z-3&uQ5Mg*aYXROi6m|w?mP7`#suEGhy5KXP|0u!2+cuScIjy_E&P*K)xq<)i~7NF=lt7N}u7u7Yu}V*TPSM(n6#9Zh9u>gEE@KN9C3 z4_@%#+_~@hATVryo7p^)4>0Pc%6XEjt3CVeOif;^s2evJj5BSZlLG@=^V&g~F~PKx zNbkJXsw|a-py`8N4T;EL60z3fAewu~1w3Pzh*DWqU@G}Ww7A?n@Nm_(mA#`xl@`IR zje+Y(+h8IjM!!kTo5C2E0rqGKRb@%P*opq(%PifONV`xvK-vx+wsd&Td8i=TB3;An86Xhi`M?*D z+OrQ7qUu(0gp&@pMk}OiYvIDF#B!NCPh!|{rimX zb^fn1=@11=%lO6v`p^gDw?OrfA%+gFsEio+2+z9)!XoZaJ-A>ol?dG-F+fr2L{ucC zZLl*~c_)ETyfrG_H2u=d@YcU`CSi4ParpV4`NMicnwP}b)LhCvGmwJGPSgO3 zrVT&>fffBbif(x#vFyObMmjJTh^i+mIyNqQx{t;Jh1?@T1%S5@XW~vTn&c3wNg;BY z;rW=}Nvof*A1qm9FpUcQs1*F9crTl35UtgPBB~nisp~>ZmZ>(I5)018aQIz!(2_1z zx3Vf{ZLr3L#_pz0OrT5^RM{|PotVB(rwv6HS2_w4;@}bPche3q1vM1)eoTgVhKLq!7!my&E7q$ANiV>!_ZkuD#Kt^w2r?AJ9nZ<;C70|;kr<+}i7l!F!zLVb!H4b1>eTJJ+SEF? zOz3uS;T9cuk+(Ha$h*fr{Rx1VJHlW?2+WJM0DG2QSz2D;O+$g_!KBlnD0VDJ0KiSK zLHg%7@9H}9m}=$7;5Iq8yhrAR7>tvGFj_4oxWEYS=y7mr*7I3@%8#DtQh9;X^gc2| zA;v=hFOVQmf-n&cOCyT{cuX!qrfIO{9*(u_Xj-Em%8(uLPO3FisIwBMP`t{9;S6hha!)&!xG*|Ao}{fA4AMSS8)UJJ&X=KMif9eqwX zXtk(7{-~CtQdJB+L1oMVG4h`n?6L4oHJTIeU=0V(OHAcHmDY(Ki53zEz6V=vWRP2n z)_{7`<4}?VG7D#uJ%wL?rPiBlNhbdE1k$4uJJKenI0#JIXPcMv#K{a*JW1YsX9s~7 zfK56cyJO1<5+uQ)yW1FXkBq=eCXgcW&?xw}qEInsDKzxToFuI+!NdpGCB-Begb$v6 z?noQ)sZoKS)nyhH8;m_$zn#$QX6SRDgc3+Z=;vo=P>=?R5j#U&af?|$s)u%nMv7#j z=+eNu;A270Txd!s_M*_Xw9sZx6;OZr%Bt$x8`7=N2*_fnH$vXAR9-W+QezsmCf8Ql zeRiQ6unPrHEZ!rx;9%0JM_c|*uoGR+s3?A_`t|yp(YDc%^|3)4BM+e?7eP^PR8&s6o`n}?tsQy0_cD3l??3Cj}50{}WVih8Uh8X3eKwTFZB zMIxh_Tny^-aWrLaob^`F#m0uSx<*D2Q19#OuswU<^IyZ}flq$m)+KD*rx>TI4bL}h ze<+V@CRR!xcKw-dq^ygk`Ag#w1*T592IoINUCkh0}IV z(3Nycy5PkloK6PMQVr(Ni(<%Cn;q3o5*t!1_Hf>MlT&1{EfdmPix~}zD!>VZeF>Si zzF+uwJp4Vn=`xDTv0F?sJm7^qTcC_WJ!?&>&BJusn@o(w*t8{?g%Mh??*ZTvK@8_Y zTY^(M5Cwz)<{s-KT#i5lwLjuW5*eBg#5BS*iMFt63~@$`!YhEMr^^(Y1b3sg#ukp` zqKXK%xX+>_5;1#n9eeNRuQdg46P!^=74_ub=hRY*vrJd`ZxPfQVt<5*xDl!f=FsR7 z^EeD4O^wc48}89l<5H+h;&#ZiP$i}jmZQuk24c);l<&-hF30FF0cQalkKYZGGulMr z=Ud*XMICy16@n4iIRHSA4@3ytXpbQ5pbe0~8QH{(mxL(6P9R!$Bn$&d80V!y8;P^o z{QL{2PuiNfbv#2sB*d9VJ*O!2NZ;bBYhg-L9k~CX&(jPQoC#D{hag-#>GmPW;blC8 z_GdG=m6ic~Eu_{IAOR83rth%CX_JOiik6rN8Fq*=u%^^*Iz|Vm;TYC)Bkef2O9OY@ zzEJ{hg=_d3UC>fEe0GZ393`R=P#Qt_kQE@T0Qu`zBQc9jv-XSPL}g&yh+G%SR#n)^ zSFXTKn&RJ%AHl@XDO24!$dAXVUm&Ksi+;TrE9M;izB6SSRV=nIbq?j@lv9+ZEAn_? z0#Wb0B>rBrWMBxSV91pb#D%X)ZH9~{;-I1BNXO80++%u5jRjcPs!rcyT)lYXP;y{9gYLe)lcuDvk@;j(v;)Yc0;1vMGdmlntY| zj7IfrHZ~SxGuyO9w`|?^$y*ILKe&6p*crXYZn<@rQWb5-cZ>EPXVPDjqfp;!#7s5Oxy z!&gl1Ruy#0Qc#L3q(T(3s(!qfE282vWs8e#*;Y|)3hnYNeR?1>oY(ds%C%hk@j&M$ znW7$0V4)i22*%8BFfGZEuQuB0wn1KF0&h+UqZObWz9Sm?!46dy1Z=L?@asH`;w3z- zw1-%-Pq9eG1j&X6V%(1LWo>(-JIAn=yz19@lWIToPI2?Hnx=EF zKfW~}iZNoxnjsrDm||?R9SltMghOC%oQG=ei%?3&7&?Mf@=GT_fA{6!%F1v| ziK$HOnb09Kgjq%=Wsrql=VCJHjdhD@GcCrVMUXZC^lar$?F)Y`0HJ9y4g%onubWP6 z`qrztHAhbsuQB5LiW&g+?Dass3LiBM$(l=S@=ahegaH6Bkrr^E95v`NfJV4E6ZzyT zA)2@Iw4hXxC;ouXzY&ra`2(tfkhWaPb|o8oEZ8XT$pHxgEUWTE6+BDdLabOlt2GG- zS?L=ULD17e!Ybn#lcb`YC^EwM8GxJAz_K2XaINb(^FaGm*@uJXnJspGba$c0jBEQ6+X+_BH^d&gc@F zqxR0_UK!4{>0-E~v2fB&^o>px7`CKTt(k_PC~~lZ0}OEUDrOj?gdRaY4G33S*s&zp z9I_0N4dn@Fd@M*~ILxkIphS73#}%60xK|dWs{%$53Ya^s^?rB z?OM_;kZseGF2*%lj-;$&fE$|F0xik-%63xG{K+)4j<@R z{h*nJk{7K!Cz)h&kFQ7a7p2nePKqQGhQu}f65NIhwIU1}@dRx3*Vr_HT=JIiGan!11;EC)z>7J}xp^~e@qm_jW?H)M@EmvinzmBF zfBMxJhR}d0v(ed*I+RJghxaxZbLZ8Ksj_#M#)j2sBx3QNm~{TgMv^l7}Hs1o=iGD(u5m$nky&t-W>QMM`HC%jf_lOnOb9zt!h2 z)u*4?$YV&X4FLt|x>io=o%uo`CEYAFu#$31E4WBMFxbDTIWGJBYfUB36?plV$+`fhaZxH;c~r+ z#puN1zR~r)v5_cmQLW_(=_qJ=w6yAjJ3k;$zdKNzGO%q#Xyz3nOOg;w0Kfv4tW7-> z97&Qgf|qNiJUbTIMBogk55Q|gz1`m9>RT1D>xIq0+-ZaFSQBN=NiD$(wT$2dD~Xo- z6?%4B42TljU&bZEUi+vfLe-Fl1sVA9_^J+3w%w&7Usg`6#o0sz{ai_@O{LHr7VHKm zbWMIJ+zRKSWrOb8?KL;to3^kfK($;Hv&!9(9IP50{@XO1~piw{$(NZ5FsKDg&;n}jfZjk+IiR;&azKH$p{Y&I9p5^&R3=VFpsRJ z^C3w9huBE=vT;Iz)$}E8UhwQ)2V?E)44+hpiRj;{uBTb*^(NCgyO{JQS&?}d58V$S zIM1&IACR#>u`T;+pobL>R-67XHa5au-~}dJrNXZb9Eho6R2QS15e7{DBM{!YCS|O~ ztB~LxBv*h0pj$P?mbYHm&)rRC=0quO@dOL&^i@EopyC3aXaRxe`~jIwTh2>2*#CPj z^<8j~^6)6Y6O!h{#Ig=UHS6@eVS{M*G_wt2UVXFVElARe1D4Y1*Bda#SS0e&OE0NC z8<>LYe3Ct^wFOU@p-%JX6YjmHW+LNhFdzpB7&M z^+2eE2*X)ZGSyDggC~g%0?AH`%dV713JCKqQse2XSD6U$3@vHl1>ivWSG`hEM!ojYX9yD{6{xRkv@~264k+W%RME)*;^^Jj^duTb>LYL!(BugTsYM!U zqm|1t+DV#Uk{!Q<8|z@656wdF#NXntWFMaRMI%s&~gB* zzwzo5xop#uj|~+h_Z|lwOv+%=nd-@Q_LL31NhUInjzCp&^*g zYl&b)oj)uq)Ff(z#nZE+j6m`HB+m)SVgukEdge0#y5*+;Phbw1>^i+Si4gnwmfNl0 z8X+caTDhyKXmpC;{1wHOKGDzlS&QfVY6qencuteqF6YOzXdQ56raP%jC4kP~M-ZWd zVg3_4{Y}b4)yLKl*VT#ZrP>CB*kYP#Y04h)GT(DPNevcD^^i@GSKe^f?c}h8+4gNc zkig+$@(~i6x4+5=$^kj<(1aBFT$KL&@XL!PXo&Z#jtId z;Q}oh&M7v6Ml0_d>D+4IOZ;chTp)Qy6q1wXu@YdVI0!iv$Qg9oa+tM{c2FOd*IwHw z?U=)%#bmju?jdgqp8Kf(^;Z=8ip8FAwEz7tu&?%IClLGG+6#nDmss`U^*0PsrKC(` zUBl@EZPT(TlP>WfN44|o2llIDuv5^fK2d7aDRKs9oO5&bEzY-FFe7M`BP|-nq`vVp z#D$&?*7mB#+`2LojS?%_z;S~>e)R;wi;Dut1mY*>eS!dcKWecGK;**)7kOYB>OQcce#moRFIL5Qoe=Y7L))G+JZouGe46YiEq8<3M76n z6*%eFD~?{)&R3&-ihFF~+%IlkPFLSwSM|e}RIo~{@=B+;qBD({!Ng=CzyYULhrK>R zSfELcLRqFHBC^TSnayXQC4f~>nc$75O@qn5b0#+#K{eU2`wowHSl=;V227ey!+1u| zsslx?SYuQ7m|PVU2NI7VaBrC#TGVdH+Ce>dw`WEr73zj-gDof<>X-|PlbGCftdkFS z+5mvB{7UFE-MR;8L;z)7BW-rN)7=~lbZzk-4nMDPlQCQWX6(LMnM zRWxN;uH9}M`pVAa(GR*i3cJ4qyOTvduH9XeMCaBEt`?E#LI6%BFqFr>l7h_bW-Uhj zxv5rCmZPeT)RDE}CbjY7EX2U9-jOdctAT~^C@x@w2^#&^y-J&fS;jl zsq)~*b^Cds%(3&@kt=#u0W9_06BSluTKDpT6em9T$}ipDe8e4Y&h9)uKWDhjW((T6 zHb-R<=XqUpW8SfUjh(C;7+@9~OA+J)m5)wO3STv5Jvv`sunnBwFkJRH5(8f_0HVeH zkvnt=2z=`$Jb@%juOR9YKFV1rval?Ta74N>QmaG2RxI*xI=F*iQZ_s?3zNzM=xeP0x3%;e)_FeM+6mBj02$t;$CtQBO^vUT?x+JjlBiM_M-eoP#I&IfZM8+LK1E<11V}te+W`KAM#DYR&?$*I0%LBGRF!(5wb+Oj(gzvy|y-ahaZk9O)KTMHL|kWOCk0v_Nj_A>h){O$}=aXYISnT;Q|vtM`idB61(`23>= zJ1J-23j439EaHYKkLzFgrO!Tk=|g|ju51WLyHgwykoKgdEx?ZNu}Pr@oY|~!JSZ)# zWo8B{F_LcuyIYZ%eC@pcKoxi*PhtS?J-#q;9yWt>fcuh)vEn+TfrM6XX;4{<_)Ms? z(|r`y%@e?TiV00qh!C1YcIr+Wlj$S3JHy_Mn{4duJZ=0je))rfCpLA;-igup#M%@N zV)d&kx3##G5uR%pfD^$IRK-9df~Ze=y2hHGfF*9vQvs97CxKZUXc8hs0EB{wD`ra~u`tE_bZfdE1cuc_!M#eDEs2Vty7OEEw+W9LK*Fds!a66li6oILP^0fW6} zOa{G{7@w)ur}YN66RyN00l2rG&EEwR)8cY+lYAzqdk9O1AkFhwP|XCeGk0cYb=dMdO64F<-1O{gw*`&BcXCmhCsWr+?*di&ODfaj|ac zc)=SO63y_G7z44+$i}aZ2bj%6K*n39TjO&UlkR#KYm;~LGuqGwjCN6aYRs5Oi23<< z_m{FPdG@mvL%(FIOj)O}_t>dZMDTEEE0jiw3EANTCx<4FBBvDLl-<+aDjSkTOA%kM zH!7L(bMTp(r2rF%E;LGEb41+K@=IznBr23L1USJsq=bveRIBks|4+cLJN>`(w?4HP zD!`BZL?46)&N%(nPa38+{Q5>7;4gi}AggOODgHDw?cCq!@_vh};+*1qUM`%2dk#*tKZVc1 z{T%o_8Ys?U1xd_FhxdHYY~NCz$&>}ZV9`6?=jzzCZZSOt{fAv#TMU}lj9 zLfH{=8ubvp(u-)RsE|YK;fC{`3ZSv~9$sIJ4_mM@Q^3G3rei0D(Q7slA{1w%7>O*L<-&ZQF`_GD{% z`>+}~F+wDm5D;~W2!ttFE`t~U{?2-FmG0T_2iuve^S#p=$(|OrU7XpE-K>%3N`YN) z4w-z0<#%(bhZM4o7oFxN#O$80tbbyhWoqS+y z)^%;Wh0(Uj@whwRLB1Nm38zo@G?G~4-Sv3M|^3ZH-x|1{j*|zjW&Hw8%62U%%%-=a+qu7kuut(l~1p04JIa&lkM?3arG( z{MHZK*^STodEY;Iexmi1&*wR;t3LBik6B~<_gA220kn-><9x>FpMJ_`01ls)Yq6mv zT0^YadOIbu+Au0(R|C|Md3Kb_t19r-8yKUUcy@_ZrgCU;jIc)RV2w5UOfi< zJcPdcTu-mwJWAhKdGy}NO8y8wvyza2HW=~`@!ktjtp+LoeHu|c*~kvEqno_^(Q}_V z7x(|d_|ZIqC-(9A(L5WPg>%!yp7nt=NFMT#b58W^;lB(ob9l_ae=kxQ|I>XQ9@fm< zF8^?DRUiNVRLjf}=j#ef@KK(V76UxX5L1@jn5rw=fAiyyKb|%7^5eSvdsmNbcyZ}H z^T!8U@4qz&_X2DMl}Q&FbAS(x$V*w(6m18)*~$n$Cx{Q zC%`Z&Jf~TER9ECJ(M-DamIPuA!nY1?>z}4Y(uBy0qc3x1<0>SEvW$5F7h`bD;OT@8 zoHLvaF=9?g@eLx%wb_(i^8PH-6_tMJ*(io*NAN!0vwQGi>Z_p0W|w8v3baUr#Ufw% zI>6Jwb-Q;bpAC|Ug5HfJQG7iZV&ULW*c`nHzciMEJ`!3iqir>g114boc>f5g#~+XB z|16lk*Kz;rePAymPO9}p0C<>~g-*Y9$p|>r*s6)ZwS%m_i)g>@a#rOJ|Cj!7>!weF z@BBi_c!kfYmMWZjLW}WP&-P#0|A}=A9D*-!R@fPU^-wd#sCEC-<>IJrSkL_$D*gX>iu@7$!PS%E27n5a+JfA>6bnwWkq64;_E~ zXl#bZ*gqOU;nu0oy+>29&Z|asLIl=^))^ar)bqlC7Zne!-}Cil|JQi%V7(?k?S#K( zdo8V%uCiI`>P}3Xb?h-{iC3zSq(=_MgGNHBM3*hMF>p zQTc{RQEC&o0aIO%d?2F}W)%ybgAx);rRcZ3hk=uhvnU=(8gaGFa5-zr*Pb-GQ*y~11!WX@NROWQHtZZ{r}RMnoH)7%)dvYaLFcQL-# z=jqi?`0PLkz{n<$anN$a&B)nrOQpzQ!Zd7^JYx+Uf5PW+OZBGq`5T@!$$f_FX<5y&B@TtJ{ntIjWbT(Y8xhY9OU&*dfO z4{TH?24I)ZNy|K^Q>68*N>|;y6|l>eu~zT!mY(*%gT9V2R`FTPyKE6XR@5%+zDZtu zQJd3^{3PUUfPa0UrXhWVhQ3H0!<%2JTI1liDxHNauwA{M-3va>hq@#l?1x+kur`ez zf?G;>#M0TTytGL%#c}Ia{~Qm;k+hL!yN2H!B5a@dAub1-0Z(BQc#}LD9ID0!JA`|u z2fV4fySsVozLRF`F+f>galRQABBbcIe0t#IK{NvIPy#DJUlz3W?jvDe-L7D-PwcP5 zK__P3l|yoGdoy9l0#J5(<02MmNs)~{?tCNN-O!y8p|Q9{77=I@j_cq|!x(OPtdF<% zxg9{1a7ktERmFdyB4ueBQ~7)?gGt0H;sX++bNAi@<(RcRw}&I;xth>U1|6U~E^i_P>(^oI2&sOqqh1=h={nEy>@C9wBM`rC= zMKLy6Q~c&HkDI@|*?hyPn$&!ml))uA-n@D`dIx7_bs=c{RtRg*1KKQv$R(FNeUN{&b;9_EpRT2~(3RFpYh|t5 zDC>rkO&i@Cw*UIZj{pCCpFXjC`nT_^-+WJPqlfh<3bIX;MjTS9kwKWX=qi_h>Z&pA zncF&=Bn+{-lGZ+q8S7iThYd*GnBNO#= zKm{FBvqqY+{6q<$NV7q31ovulH-c;(UiO#dvAXI`*+n0ClQwrL9tGF3xLWyK*<2@V z;gySnv77fcptA<@pDj7tIl3EH4R!e#^QN;MM5zQB?Z5i`5HSpi6*sW(3+#`$yj8d% zBnuM#RE`6ICbUnQ-l$$zLyYts^=?(i#crzh&-@BKViM^Xg-8o!IrA2s75T(-!?zP7 zy93X~2~kaH@XQHnD126HVy(Gi#-tBy?B>`J<`w4K+lE~&4qF|pJc+}<=2jZ&&zdF9QC zT(~(k2-^wrACq8a_0;)T8&Az?ADkneu5#i5moEcrQyAx=@pRyx#+nnZsg)#{p58H~ z<1F)U*$ns!`q=gHQ~co8Io_jU&381SE*{#Ons)%YZ7%6MYMplfV;OW1*Tv{!mBMCwE?cm+b|uh2FXxyTkwTjd zakGiUs|41|Qx!BdC6kjRFzi@Fgr+@}IOn|xbs0>1ah-2CpP*`B)1f2EwPrkQi;cB| zUE55VECM#o{)X1;YO=MdrteNZeRO&H@bvV{uJDYjM;kxlWs7Iw8|1b_%LHSS$)+c6 z;`hXFLKAy_huXZ4%Rl7(H?E(jDZ1h4;CiBg9ysP_)u1|V49OWygYro*cE(2j9$vTA z6~N|lZ0ik;&6>7ZZ=XA>`*NH$EJSI#^%v!NVp8X7+KS706?0bmu_#kq#-dj2)_75 z^K+<+E@_2Ef9swzfssKNzD6sPxJR0<`xQ4xF_4s7lvxe2s}xikLz~trRB?|{xS0_L zQW38@5#9@pLKdB24qch!EWE2Yi<*cdTT~lRdRt4}{HrSV#u`LP4xVzjtJ#Mb zXx!Qx!d%*G1;~lYYy)5#9m=3`p9iK~1_11E9gr=hkIHp~0oY)yA{xTzV|u$W@UA!B z**%p`lZJ#$M_LXj%?u0OyluFfAy+H{cr!K;$jli{IEwg!7&cdl*O648;j|qpGnh2h zBu)TWK&QW&OPBTpW26nnR7y|+Ha!D;`lwumi%nmfYC1)lQI{ue1Ggf{G&?Q~>mAHs zYsya6^}Br;sSjVp?X&APO3Al5yzu|3CqGAMF#TWuBVyAuZPWDRn*YaOYV2wD#P>As znUqs9kv!LD2UDefJ3LSD?1GPaaD;}SC`#|OAJ^*XKo4%~Jwymls9!_8V_XLPT62S} zI^z=cm#oLRvCisp*5K(Xx`j^oYkzI|W>@&;H`kT5@;%@4*2#wdequV=^e;}@-n{Mm ze)%5#j{ExWD)jKsk8Q$nTXIG>j+8`DY>J^Ou0JKEZ@n1v#sR>5VCKYB0$uW=SGm{- zm(75-RH@>($9M>bc2%|TTCBFX^sotLG)c@Lxr3J*W(!=K(0Xpj-tb zuRH(;jb zE}m(xo7F}^WFgQjt2_XIJM#xMkK}XysJoLlQLV%p;5%DNS<_LHVc_uyv{kk=PL{WH z{3;AH)c~Sy;4n2(l?g|Cgri-VMG1HLU!Z~JJEaV3yq0Zpn)EQTAPj=NcKWW7S z)BefN^=mYi95*~{6X~xcD1Yb8|KH@v z($~ipew#l;a0Z_<+Q58^j2<2H-mhwG>;~f_kZMnyO*EUVm-$9-u-+UpSRFc^y+J09 zRb{*BLXe_1kIN4+tJ72Q2$`BV&OIKnHaX4J9bq%34#8?Q%UXs`vCWb*aZK}+xw)jL z$sG`vw+|rQm=?z=2R2j#Yl~fP`+3jmwz)33ip<$LD&y1O$H(IL0u%7}AX#9YA~|>gm;s`&xNb(G#1`t^8-L1*#4( zUIN+=FGAHXR|qoK0c*JIgunJ+N5=cz(nfh}tvk80Za7)DX*t>Sd)_c@EN^c4PRDQc z*00;GVTj9ZY}0zQQNVH^lm#id|oeAGRd|XU&D5INjg6UTAuD@$?O#Q&gw|#L4^{J<2VobitTMuV= z$e+rs^Mg)_gTal%DG#3K!fDA675`EfZx)eVNKYIO!t?6vnz>+3Qx>}zsl`-?Y~33n z3^lgl$x>`rA_aFr!;^PpC^O%WG?LVXovC;7QGOSdq<&DpAbrCNO@mdg$e?wj z@Qcc+j|_Pu{szb{=*y|<=ab(E6F6|T*`wb`H9yAlb|6$cK^hOdBTx$^-f>*sYz;%R zKqx5Fj9ElSS4w3`4H@`2Cl1xTmh>$(0B}}5Lpa5LaOUdx=%w1cv>c*UmcU-$YFNh~6^JO%@#1_nICes8Z8Wwc6 z;FX^C2CPzghcQaFptVLM+Z|S@4$X>0y`@n8nu1gD3gPth^fHh5a>9})Bq@Nao7Qp` zX-x{ObL*KiJ5D6M?SQB4<146gzzW72H*yG(`v{o(HQ7<(YPz0n+Xi?-7#g4Z;Qf%# zlDcRY(ymBMKzt{Yy$n{Ya9@Et_~;*l4~rpjM4`qn5-Ww8WmFBGi~r zQT<7F@NkW(2%~Tr124^E5U>nrwF*=v;#f|i6rr>UmBk|EL|;GUeqwV?W=pkYBSwLU zaK&5$@sWyHvAiUX?U*$UCRX7QMj9)r#!@utxov7%4^U~0!DPU}{9K-zdFrA<*S$VG|LCJY3k4q zA3gFFef`>6bqIa*$TyFeHDL2%1>1l26Nt~g=qyj)f9kX8^}f=THdnfrHtRmKuG<(+ zOrP19-q~z;=ht}dx8F-YOVh)PSI2S~fL5>}$ssF?+CjJvG7D5&ATKO8EU_Z4^yOVC zC{jhD^g3a<40oa<5>`>tmfIxdD)=gi87>+?h6IzJxqwyQqVh%cjrxIpq&`_w=%=*x zGLgxwXgw1r*a!J=>9Ig?F<^;XN^t2I00i1`O;~LfblR~_t1cj}WK|cNE`fPxov?Lp z1Qv4x9eB<%7TH`z)M-N;ZT3=}2CDXC@{s#zI`?ggiYc*P@_q`WX%Zq!-Eh0vfgJ8e zTlzQ%u{!p<=sWy-7MxSV^vMrCL2x@Szr6A_Y4ZgEGA<`xC=fzrygVA^kd`mV@TIr) z#yN4Ih^(P&+NMy8qZ%|R(`CV`Y646k07_3kThL5ZS`c!112gJwXViorpyeAmkWB)Z zluNmY%GkJ}@S7S?nb2h1`?c>g5_!WCD@g?v*Cw0vtZHQ(Qccp}OU}759~)6BRtvXM zNL$sm3)5)`s~>7UZ$<<2FC!jz{=vZ`!jENby3gx0ETvIl2$eX`n!3W%LpiLjIRN&p z+mt~rD06u$QsYN!>5;nrhGI=L0(44mM!Mq()F39@vw5T06DV{4JODQUtw3#9U&!2) zxe3?@w2bW<*!{Adf2|(^pgd^m1HzS%chp)eWCZJ8Wzr=MJ%6IE)y^9l;1-0^oIsll z%joVN>a$3>bfb?(D+B4GN~?#u97l$QhO_2PhJ7H zUops)gT5wa6^#u*U`q3eR4dHX#$JKh+GT4Va~%Z+`{Qi6 zxOWvz(^s>P#@BwAy?yQVvDeS}-uroFKVR_5`*G8xrs~1bWKHQf(ert({;1EYdb|Ys zprJ=?uc}4vFR7ve#>6}7c08Q7F=>W^k~a)|IvZ9^PIb4_TGC%43#iptc6W^ zDnwH@R2HR;DnM>L*F+_eO0&AdK00|&_+tb+N>!{K$rJY2+tL}%(HKm~Iqs+f{W9_I zdGJN^6a96x&>TIct{o(?rV2O**GW@V9IO=6N7P}%cwQ0|y^1xPqe0j<6&wk%p^QY> zJw_`=>v23iikw+3?F3dRNHm9Y4Rb}jdNG`vjKdTu9dRQ>8xt4Z|CqEf@(2B%^tg(l z@F-$HL`f>2HXxZBh3JrJD?~%!9Xii!zoh$c-o9+p!F}`qYgoZ-CgW7xZkAN~Xh?St(f~*TWrQRd?1n9R$h0|CV%!@Xt;|X?)L$MsS ze6r+BubvtfHz$E?c{Forh#nY|Nh6M0jRB*GV9%=5@VfOAGGDwM!^fJO<(!@_+9V3< zmCVpvD~|&?E}ek%^N2w;VxWh7Q;&3?WPLFMrGG&Yr@REac$yW5whAqxACB zz~Zav;&n~j&@AgitmK%UoSWOhbiybuONKB=D`UW7c!UugeNf(aeYaZW5b6fZoQ;nS znLOSHaAb$F4t-NL_?%AA;s^kVH4t%y!(mvAeH&oHuK7+p zl8_cLl)p{4Iw%@+*lt0aC=Zce24u0*<<72AHXJ!}rOZ%(3c+XrnkrxwRvbtK)nHPo zidCricnvnR!Ve!{IhSQfGCcg+4~yoLr5U~?P<56xV=Y#jtPSj7l1CV}(E97NBiSob znBfFy8hyJqo7d6o?W5^qub;iX_V(5I=aqim{k$(YKTXx79_0EoB}d1H`hT&r)a!QYSz4uGFj0I$!QeqyxXCzGprOKB4j?0o zea{?KR+@q&6EtC1yoXL$6zN{c0al2c@GU`xSr$46lh;iw)oXXv<&t!k+Rd5_YWmz|in^(pG0M;A zy+8iDvD=<}FABlf+U8(8ASCryh(07HrKx`*7&vfJ=Z4&Q=h{^9HIdIm*vyiR=q3QL zIWkR-8!clchEdf?A%ztH7}oKTTr`!qZoZyw>}@ml8jI_dJ{y=3L}Iq#m4=avwuLdY z0#$_7xy)Wm16HMFRaWr>j_(#OwRRQ48jY;i7r*hmSJt2Y z^j`DRztY#9pI7>)>F1UH+w;#W`_E02ny$VvY+W24hSii#X9u%HeYk8*o?EvRY-@}RS+ z0-HPr%y^jJetJVYk2P zP_KQWOSF=X!uB*C3{$Rk4`oKuV2g(IFkZ(EOMG4qW}jv*f<9fj#EzTb!9B%f#TzSz z4vE3mPTJ&?oA?8>_8WGUP(;1_RqMN5NLQ(ClyVg|kr&D4WcJw74FnKb6^s`(lvsVKr87EI|hqM)>os<=thCt0t0K~&53~6GZnx2>T8=)qGIFBy8XQpP8AZ3G7XffM z4MzB|=6W#UB7(~VN}ILSwH8fv4Xaolo|d(oa0pj*wnolaN*}N6 z>*dXq9wu_iO=p)*Z_bXEql2R!!?7PZ9?`&4V76e?dv4oiz53W1ODyI+U+6ny^J1(o zXkYnU`wAa@wpn8xe75o8D=)ZQ#~0R@f2|Yz*M6*%xy=+(_$8bD+Rgs# zprMa`+)H)osRzSh%RHp)w`mw2+IyliN4?AA@vlisFJ9zZa2T>FEGm+C+G0%{vtdF> zx1{JIn^P~Aj)iEr=)@Vq;m~_*6;HQnV?`z&8XZ-A~|stxD_+bxFp@cmO7W;HraTDg0{maiRL7z&=2=l zC!yh&ar4~eKKL#t+;|J_0a#s1P;28o(~&{mfNK9jmp$X)(OUnHhu~O2P|8v-Ns~#^ zUv>AxR%y+oBPz}vKK|+c=`YqF&X=Se3X^Yw)Nmu+I7$Z^${Sf5Vm-G;nvJ=+q_@d@ zW;n#b!`{-Y))aKn-#}e8+|)EeH&z!ko?*3OU=~))as?zpR&)z4I|3RHJ=z`+rjk$( zpZ}b@!PWu{nM9wGXaoR4hi;j~$2|B=z$!eCAmpJs^*2aH9I+hb!7u zSF-Ncz@rB1@JBrI%SY>W`RM&lc(4|_g|5&oefvAE`+22bVg4`w%XdBKM;`+Wd3$hR z=wW&q@`(aaRhp7Z`oqd8UK|SZTN>0XdV8xvyBeW5Qw906rb8FuGuN^yW>gd^^U8ul za$pgkD{aPDk511SE=3!LK>ma^zm_GxQRJ-0-NSaBW?tjiBx@uXYgOh0<5`n@6WWPm zZ^$+k248h>lIJp=E;Z|M6=^HWenS?F$%_!F30oX@iN^964fh9|P|tUAYcLIYJ`cj~ zuS*op;Audcu*+$)yw`8OSZ7`l%CI6HCmKYlJ?~a5If;#2Xod-BR5~V2h-uT$lppxl zQ*V3xCou_xEQ0qIyq%((^9D+}XSlSI3S_aYF6c_RNJ%b#AXiPA3XYb^$7RLKHVS|# zD*UaPSExQ`5w%i9ZUtAZ*;A4-N}iTYlu0l&c$SY#e$qfqYCiBfL5f9V>8v?EXJtpJj=nHWvLnb z3!gvWrQFFQXvqMbISgJJfaZ~68tHaxKrR8uG41w(p&QZifD`%R3qu%;Q&%_AUril6 zYJ*`Nk5Y!yeLi22Qz;E%ThE0XNRP8@g;D}f5E6^%poV;G)yk|7$ z$4?766%{*DntE$0dwP0^HtKE!hjqHH7?!s+1d@m(5g}6*ZgtqokzvF78JP$4WaL*> z5ULARVM7#yPoWXM&D&83q`bF=QU$K-T~d|`vvBBEMN{d0nW-cX(@QEuidJZ!0{vks z`OW)n1RBU**ESROxZ)BN-pS67v+Yu531nc8vEy|T>n=w`Mo z^Ej{Pef@XO@4fT<<;$17zkKn@C;i1sZhH4m&o`GUq6UA=LA~yeE1;gG?6zOq82#FN zgYAmlmGo<%`6DW$FdmGwH-mzOWrPv3v~ru4HGeqQP4tA5eK&tEGKq#>s~cs#h9 zohIzzz>tT+0hQ%gqTP=<>ePMW>O-%{Cvx2eCHLd7U#t6f#durRge2yvxrl|dI^b=) za#VzGn+bOeJe^htW&-B@E*|i?($u&8^s>qr(MLwqdkbMfhzA%m7$?9aQ0#f@ZV?+B z&B8+qa)ZPJ#fB#H4i$p2ylp-w7!VYbuzux~v~eVCIU7)RU6vIbmSS`NP+p`o!gO7L zN0G9bfDOZYwSU9xrT+)Qd7_K{KpMCt@keOvVI> z+VS^T)?5CE*bT-i(?;Zt03MQ6XmXf2tNuGmkb9=q2v)EiWaMtbJFL4PxQVIy3q_s! z{zD$~7@ts~+K!agnop_CpfI4}W^g^v#GTF1aTahvNw(*`F4qRqijU7-OnlPD{xiD;-RM!a>9=-T0vSRwQK_{dO(%JJS#Rk2cY9q(sN{qA>k#blPv>WT6ie)Y=oi+)c( zzQ4|LmuPz4c35rvRTq_dy5J0sbsW-fnIs}u$<5xUZdO}-oCUZ zz5FrPdKOm~Qb^R4g%&uu{|rgkT9pJVGC$hKbT?2?xz&*v6H!^yR#-mxjO)gcA7Y8;aw^? zW;s-@DQ}N2N+aiN{i}whKd2^sE>+Z0JP4@)t1Xm0QF=&zGfAP;56uFs8UtY#F7FI4|Y_A6_(6U1PzO_&TUCR zZCrAZF=J`DD&W>9sZ(Lk)#9>ntz(Q8njJXiTk!AD`Z^uK&Z+{K#eTCQ0jt-xZO6RL z1E*JGb-NfWpN&NTa@%2NVXp9PweRESbm|6nF6{4rKF}%V0}03g&_q!(>h)SJLYL%; zp|04ju1ZA>%^kKqPvT%+15(Nu{UeK0LdZ%3BWk^-=tOkW{)R3SFfdI_B@gox66>-y zufzikfLGO0f`}*(HX9m&1Dt+v+LY9F+E~9XdVsuMjh_o&3&gBKET8ZsS0#v5t2Aax zrhz4W#m-OVK=#jb5M< zq_m-Glu+g(Bj0?qW7~9ibEWUb@AM|!b0b>Qy$W3H0xk)@USnf7!s5VPVrpT)I-BSb z`3HGrdFL1XAHIj>yTiZwJ(XYJU;SRn_dNc*?{%?L8xDu935amYA_#o4^`%S8$R{m? z!n`815@`uUK}bT5WI_|>2hN~Ske50=_Cjgcw`r-TG3K1of-fO)ni#mXH7qP}slFE{ zQu#iHWsqb`S|>u|U-KE0HfG}*tBiTgKQfa(OSMMTE|G-CQXe!2Ayw#AkfFzD=r;@~ zV>BcLX%k~=Zg;9;tbVAHpR-23=6~m_`63;~vGyj2CM&Aaa#K9BczCm~FO-o}CLW0lc>(Z=S6uFN-Q4iQ$JkhbRRS>iIysxC8>AKa+2y~DXOFilSpBFQ$M z7=q&-f1^4Ptkpcd;yC$j5~jfhPnl5-ku@zql3lz*>80)0tAmO?29Dazb*eFO1o%zPtyR)49>DE*A&hF0XNMN}G>Na=7DmDt< z;cwfOr>(1-z;3W}9uJ6-#qV0a`iNvpsgNLJ4 zk4BTDh80XS8DFVe!c9507qXs3^D&D(Nq$PfP~`<+yE&-@GDz`P=w|2<@p1l0>s?7A zW(%B9rbG)=M!M4V;ym4yu*7ksz*&gM*pEq~1yU0SU301c#R1=uVO@IP%nfH{H}H^X z0${aT5q3h8uo<~HH#&2@O{{bSc3JYYJ;=$~Z)eF<Y%S67mu)tstKs%G!N0xl>ZC@Z293=FMIPbA7pV^xl2peTw9aT@7hdx-%CbUK{eKPB_-divJE zi;XL9d7FnKSVh-;qFvHao(355(P{On$n#h;GA}*+JbM!rNpt!fWp=)Cidze+mN!^^ z$Ce-ily>ZR!zVojTuL#B`rJKd=DwzKvb8Eg3OApnHPmXyYM|gDD0TK4(ym;g9+Ki= z8XfWsfYngN_&|cVhs08S4dhbLLSOZ6hN4ehz8yIc+YwZ)^TEv zddz}LElv$?fy-y2KqJA7qtNJ#Obx4~Y+%J@KR!**paHN~%6{6>lN%qu6g?!Zn**uM zx}{-sEf~9H@;14WH5>wL=O`P`C#eZCK+;LMUuY^@Q3Gr0vg4pIgzvMR&+09pGlr)lX6`H2`p=?a$*<0Gd$sz|PNxsQAI51FEqYSd_7Mb{ zD!~hS;wo*UCuVtefENU$C6lldGR5s7tBr?_P6uTKv*s2IH4*)gZ^*+!S+vcKvh|Wt+_dKIyE5twBQPs-5wB?yLI^jG4b3^GxJgcAt zd|7^W4y=&JSz4M*OET5N)AYL5mYV!pV`#x8Z8aZaQ(1g4007_Ge#H@?mZt3c4Z&jV zPS|kYSk|zvNfW5kazhW0#b$XB^0&UzjV)NPKt)PApi)c$6-9^mR*_z7)m~YK1sK)& zmFhU$n^`9!0>CWzT~!z~TrHhDhPk7)(bckK6TU-ZVLOOeGnyR)gog0q)WJgvHx$yf zODvhSfTGH@qG#!#toorsfoU=(+no=$Wn9vkSY76k?C+mP54j`fx1WtUwIO>|rf!gh z%bHoJ`}~h;)nD_fY@zjh0tj)uri1B5MQ(2JDb=!|j;TTdrAd%9gmSvLn*`u^KCVv% z$!%e!u>Q-Dt@)ga-XlzJ44ClB7vE#R*RmwVekvG++^p0*e`mTI4Tn~l&{)_NgL2%_ zDH^gsv=JG@#gT*o#nIU^Adj@Y(*GPUSRuYC7Jo}$eYYpC&jE#327tCt z9K4bF2Y;gqAp4MOo0!>E11)pdkx`yd2B3*k8Frla%G+r&*s99S-+RmmTeNMeiH7Aa zOWT7M-z{mbuDk*5#C`WQoToMUiQ4t~;(9|}xZRzpNRRVz87r|wQqv84wwYAywBztX z?%!5|q`oFG_5~!1C>w4^1YF{<0nyWW&pID7W_YM0($Hq6gyDX82zGEH4~sEwXPNaNQSGFDFinG89-t% zx3n}1Tu9hL_Oxut+ycbpHK=~E#O=fhwX<5+bpSnpIVx2AjbM2>2rM;hFBcGCId;wf zrbr4!n2CXcUD^Q=`WwZAfp2-eEVpsag17^pMw6>ZJkj$R3K>29w5IdM!+T$}W@(OS zT5KkytbjHoKmyW|is9Kib=*$EdaZB2s&DLLoBPL|=N}}E!)SRJvrw*jO-*%}(MJ1rJvV5e;(K~RlW=jGOjQ<;ge zR%5nHcN4;))vgBZlx1-B_O)0^)+AxBi(ve2CbT&$}g>o&eW4IQZjC<`hR%0d@7Q`ax zR1g^O^gV0`Ru2J2MzYjX2`MXq)lZVZ#(*KqD^!iJk(OyP&$KGx@_CuSg#dSU#JVyX zN(R6vEeS;3g~SfO%iOG4q#LbBrGR1$9Hstai!lI5rW+GL=K>=j1fmh5dH5>QnJP#2 zc!FkV$jES$dyJdtx&UUi*yF}~4qeb5tgvD_U=Z-UAd>?~8b+46orBI{%*wYx08s39 z0LaQ@P8){AHWv{`B+NBzNvYHiBEo1eR$0kP-E{fQ##3(8uS@AM`c>zgs(+**ehBIg$ zd3JLe#qZu+eZLvl>4b5}IhVUxrO+^K2X!~%vK?>7`}YI)8ml2-yJy+%Pz-r6Y;x05 zk-i$6Qyv#vzX+f zW4!LK-Wb@}Df6W3!V0c@P;~|?k=_rDk~rp$?3)GzzB5wrH@wh5Jb@sPYuW^|X9Exe zM;l{Kl^8(KnzVWE8a`dq;S$Jakac5RqaU8+<0lG1S{~M|-2*EhMrRhmAaKyw%B66o ztO(5$uw|3+Rz(5^>!aQ(M;lhQr+wM8l;~^8fL#*yMQ2u=dvJ-?Q2-a0($09+=SGI- zyzYPX`NyaUIwp5HZ3Gs|Z6F>Pjf`&&DBUiyxfV}6!sI*W)H9rr;j*iY%>K`Is(A;1 znn?+DC{cZ2>OBU#Tn**GuB@>p4BR#V05BGIiF485NJlHs@YLVE6y zXXY73F>dYAV+$qm(QsxEMp|eznP6LS(17Q4(PTm?yIuD@GG5k+!O7a6FRM^LL`wh6 zjlK2;1MGx8Iqyje+AzOhj-ZgQ0%;kCt11#(8uCI_8I7>|hHBP?jI6~V=}WW2wg(gG zECL5RyvIR$K6P=xXalsN$wCEdL!cC`NI@YJ(u&8$u}tO#hDmV7PBARuOTz%aJFdz0 zqJz1Y+_+r0HO#zUa3tjSaEn**t*ZtvH|u#(o_t=9%$`}|TF-6dGY<}N9M%v-y?s?G zt{}zYZdRY+MwY~wF=<^hBvEgIs%Gx;qY%VQl6@AdiR>3e;C>IZ}p z1WjVrlt6i+QPvS`D+Bv&^PY1AEye(~M*PceJMWUEGYmU`uqt7iT+5sQ(2V^sRlM9x zBR^aZZ?bNS*lkGTq8iz9`tkMrFJp5YZWa=3VS?d<=c7HS6RhHOV>%DVV|$`8(cfPXE>3K2MZnsQnT@uq?p$jgVK8Q|3AInKp7zSr=cfy`}ir3 zdH*=o&u=MW+U99!mSp2X)O7`}0sK788!1c+(IYCJTT;uvC>UIpvE4UoC zw*4|}h_$T?EsLDG*ukLnWvV!+%CWX`CG&6%uv(uI`_S5$=gp$$|ciLv+ka0i1%7+Z$0N?u~wNCbt$I5dHM#LpTrXNtETQ zb7oF1u4kt2j~R14p-qw`tikh+l^Tk(3cF^#_8*WH4E2_e!4J~fa`G98Xt zYk=dgmz*|&b6VQ4*Uxn@p;UA$QGq%gr_e}n8nz%wbIO=&v`&*-V?+P{(TJ%5#HzP7 zaFTc? zvNqjK-*PMr96*sZiY4J0CERaSp>}0Gna`AQhW88`Q)~MqMY# z>Ze)l6dh7m`m9-cYJ6*RKi~gs`*6$xm0rN?JQ11%c__jX+BdS#R4R152k+l#0M89*Ri*^_ahgRNUZSQ(@^!+|>xsGiR8`zT2(B1Yl(#eq@ZD->UCYhA- z8y;1edTxcSH7b<8$?9R3)d^x?0SfD3J_N%r# zpp3JswVAF#5S0JZtX#QS2Ry@dN+$c|=r$cFgy6;m((;Hx8=<8jAaU8_TFdd8hRBxO z5m2>5qg2BRGm@sOAC81FW~=AHR!uCE*iAWXjxPO}857BDOiysdc5MKKkzrIQ;={b* zay7AfC~T!@P%5fEymp)R-gdS97si1hBZLMBzaBNbJf{DKVggez6om|NPUTek+%|mQ zQt%X)?OhULFbQ`4)h`y;4*;>pSU<+RN^Z2oLue- zL-?u-!_zs?%y@#VWG{eohEv=7*-?9$d-B?cGqzdgv-CoWEZM9FK?c2$ve9N`HMAE3 zPAlpjbfi@&f}f)p5s;WNsY2WLxUh4?jFiNBfgF_^BAQ?T*4HwBgJi4r91g|FAbVlW zBwSB#wShnEL;A!*s0gCLCB^E|#`1&+5nF=ufCk6oz=e@$a&&cBPDP&w0hyq8#x+*e zaHVa}+hU{1u(5S%A4orCInlEm^2wtB7Ry_xd*#tU_{#N9v_oY@ovBP9H!mxj=faj_3 zog8v~Me2F+D34Sbx-*fjG~aO92ydhXK0Kc%h9@1rvN62j-)f2&@im8|mbmWRCy6rs z{6yAiaZO>YiF1)Pjg=s3Zm2jRVP^yiM)SwwTB$4d@H1Ie*;^Gig)(*FvFMG+#&z#X zk^`w}BLthKhIC0ceyb5RJW@dADNhwzS_djnNhlCq1!LlznVMv~8~2zv134N_ZQB{_G$@r@GH}4{dm5b9QfSfExlkSNt>3zK|H+g+ z5?k?>bek%kP%TxB72v8xGu{|Pd}fi)6n!rSLg_7&lhKu!Wu0Ay@e=btt_Q{B27Op={0dPAN<(A75Pef?~!$!E?pO06&U4VmU@-4R{-+9X|J|`Amd8L+TyrBIb<3VMXv<=-46obkRW?k(_K^>Cu>=5Xv+dJ^KMC!X zO_55aJms|E4V)tT)J@PH(lAV>w${$_+c-?tR6`wzin3M-<@U8kDy1f*ItLcmq`9p$ zc`!~z;4-W-UKtyCV-)BdO;M$AsLDpI+@t4wP*Be(5!aepG0F%4hUu;uDH_&bfEvmB zO@rz2U-LL~8JAqw>E1JyEwZgG!?F`5d?wadgx0r)Y~R`%b};FJNJk;O3dITxXK*3y zLXx700TrBr+agvEN+#>3!b+?>=w_cC=3%FY1@tr^56ENoExx_*waaN?u3L7#I)Av} zsTQiH%V)@0MLX8Q)VQV`*v`LryNh?IM%4}Bl(d#vNS<8UxfqOq9z*_ydIHo){N^eR*k=BaE)A2mUOsh0<#6w5p(1doKvD)02dCBZ; z*?fTr?9&#D-^9;Xu`lAhGd9q`IdF?qpQ*EdJh2)TSxlNI9(WAu{_*&@Sx{Vvp=Qw( z*z*`Q$J;LTG6g0A2Q;K@I8Jp#N>XVUm2iV&vln^*t+C(k{NHD)4ENePV8tgke)1+9{4E&CN$(Rdxg z+3x@~3Amzo-7Q&1cveeLD)v?4&=PHG-~X-Z!Rmtm-ijoXg;?&O;wH?ilIdp%R{c+n zR|+TLQ!otsC3W5Y#cB-$I(4R|mC<)tzIw=})l(6^14?L!td#fXG0$l3n!jU=R z0-AY>VJh4_9*)bPA_;!u7X#eLNWSp|Q{|G^cvuSY0B-O*`2ah`(*J$wegHsCqVbh^ z$ff`QjLkb7{o47~*H-kXw^0ttZOR3kDoVfzN%y|Jw%m2XrrR;lt1a$C>s zzQ!Vcx%8xbR$0JX-+BXqgfe2Y3S3)3QYttWP|M-OG_Nzryxtf@vCJ71PK*^(t#3tj za?if?E?ceaRM!KyAlKbs6}Ymt9X?~<>ehD~f*ed5kef*d?PX0M)DTcaBmz;;iXNBn zV(%EIDIIR-WEy;6aq7P1Q%?3WW_jQ+6mw6u^_S4#!?aB!g~Myz<)#1R!@|MA z<2MqcgBc#Xs(u)4JU-c=PYk}Md#<&L!>o<8h;nZ6bSq6#>#U@mRH{UnfzYJMTzfSh zO_aI+`D!qh{>Hi5>U(j=j?R33Yi+!%ETAZgGGKH|*=_tLoY6ps%FM@o>u@-ynVp zCHZ}pttSH#5D7DR%FOQX0dGqD*d(O~em8c^#b=qgoGV+Zn#+avD%VVLL8#TFu@&fu zv)E#auisbkRb^8ibUHXrpp0z2Hj5syMXUDiL!9RG|z+ z$Qllwhl?SiMv@{)IFM~@O&+&!Xa`2AXkN}eM?`;4N%o_~7i`^{??HRemHt5Vu^t@O z1q5Hzh0)y?=*E&ArFZJ0ZrRQr+6hts>1J2G4fZLvWXUSv6uE0s@GysLrOAdH!;|6m z78|h<$&qU?`?&ed1J?jpW_Fo39B4y{Y|0p=I=v|ZfLHGlynBLjg%4|i?pmTsA_U`mq49R}sO7&6rqT3sZm&Yh=?y7uzcfDtfdK!b8tohtGV z4=;A;KOSt@G{%>P{FKl(IY@_vjsO5CPy-2b4w>z44q~N}iZsDAEHfrk+-t+}?CO|4 zE~9*>6lwdgJvIh>%T7CooySnNq#-Bc6X8cL_0@k2%>1f9?ulO(pgpi${fqZ)fxuW9 zQQYu=I5ry47ezzGg#M9#$9P;**7wv=#iWgELu^K8u2NG25*y@p@b!KYB5`p?n@7X` zH$8rxXzq@mrwmtr%0NSe>0OoDLFxyRQs_Q3@H;JMQZ?^G31vNFdF7J9(sR$B?cqE&M zqrsz8?x{({G$m%?fd=-jz)gCWdT;;CN4EspEV5jz`x#z6rwTz+w3MtL0&_10wW9-} zE@e=LR*2T=AC^Rg+q(Za*Twhk*~`L!bt#l-kXI-IJ8D&<+S)PJ9XmhQC1;jy4f{1s z6adI;N#0@dYrCgftAWH=e}0v^AIh2$DZ~^jfi5@vgw>WD9pD|H3{uXEof_JlOM96z zRK;M6&=$s0uRVz8Q4F!@F(f82Dp9F85)Q_sC|ki^Q&ph#xkhPIfB{3+XcUX6qH>Fx z4W6Z{OR@;bNWmg5@|=s}@x#};&;?8q3X1%7pd>v;JHS`c9t*&BSH204E?+~k6S(x! zTgnIHz@$0Aq!-b!iXigVjc2iOiFBsGYG8i#i2a))7n|TO94#<>#{~fc`?vOkwM!g; zwPn^;0Asazb4+H(f%o&KyV>3)H4+bzNpCyL0e0EtxFsEf^yFc$uJOs8(%sNW&qaQbZP5U~3D|Hn+sz{ec(& zEmyVL1W2_FYC9pMW)cjFMTmBxCE`O!U_gH0u}g*Tw8=_~dwQ4DzFX(5T}IuL4+F-x z>FZRY`Q^Ya*N}%xmb}1k|G6~iHfJ^KA6G4GWPwjOPNYu{B(g40xdIcCb%~S!ML@d0 zeZat&N4*uyhLN(v?a%Zk>B-qeN@^fD}!uPwdQhU)$Ok`xx3gil=?6!Pa4oETStEyl|y zucuhh%7Zb5wF!qZ+j4Yh($;fN+^|KwY^i2ynXMBU_=G*lD5RF2N0^j4omD>qzOnz0 z3I>k1{?!kaWu0xL{rZsoqGi_Be|B;?zwiS+cEG(y>J~nMwH@1w^&EUNh6Wo<)^|C% z-LO5|UmxI|(L6WRaG)_c#@8L$HdN;!!OIYwl08H4; zNTQzQI$U5adfA)nXe2r~S!JSP+2op@+^gzj{OqHTT<%x4YxdgBI|{An&QTG7wWBN5 zd_*FYl&(A~D15E~8nne5S@ciLW_lDu+@o|ZwFouZ0Gr)UcZ*-AN&&C!507#Jy!R?gvILPvI4`keTtcgv<6vi zX7*G0=FFLD4yqNc_fjK~U*IhUerUaezv{7He`VZMX)ha&Z&kI{I|~5wNA_aA@F5^dUYNy!kClf{A=j^K)Xn7j1R0Y&|Vk*fpFM!yVYzyQnxR8B_+4+iuP3AA|x*dF1PJO zWSw%iAV4u#?)a|HA=HzL+K2fZ!xQ<56fL)4SihEDtV75kBrBLND~a&a(vaxi$mmj@g1 z1D(dcyYAavtX^JTPBLJU#f64;(>mBg7YD|SGNsC-Dr0ID1PSh|c0Byvp93iS1QY?q z(E>Ql>A&c8!OewB4=-~WU?o}u0GNbc$Q~cReK))tz9k(Lm>3Ffhv52e3IXjBTp#CKPDYCm)8(>Gj7~9Qs_;NiIQ=B_D?{O)V>c7eoCygXuC4=84l$lFlY+~WHX6ZOB=C%+RnYZ!bx5(gNsX9x zYfS94X=zC_Ov#Kv?pTTwkT*E65j)$_C>(p}(U1SQB^hYq^I3(~P%ZTPj^zPRWV6m_ zyTBq+m673&Ym`HwL0e&D@>;QDzk2}ozz~Bw)XeMzK#oSqWPGKs;hW`C@qTh`EIr0C z1~@BXv@%i!Ct!l??1jFfo3*GCYAi*<5*Vg7NHz0>Rw-OLdo^3Ow3Q5$V0cP2mDX!V z-oZ{m4@*nH0oGP$>v4cAMUL^qSN|t_mc8I(`jY+Wn>TlV_PIT*naPR8np#y4@Mj2u zT16|Ie2yyhFt*=hw1j(N3!tGe(EIMTA528h;L5SX{>z3_L0lgaGato*&XvWv9ILL1 z;j&KOp-0W?iNvYCU^cbT&=d*QA4^!Lo5!zxx-xm0>#aVp3WT`|Dvb zIS31aShIuCz&O1OKme-_gh|N7X;IVAB-N=Hr473BID=aU9Q2O)VK#$&3-|P7NzTD( z)O?#VhrBxSm^u1*F1OuZg8?Vg$8B$QFqK6=#Si44sDLaLdzIM>zij085F%O&^CY>9 z&rUPdno@BwaHPljy^x0KR`o)&db?QfleTeTKz~eq?Cr7nocdA5s9W_-RbU@8vbeN1 z(Sm2?DSM?$a$}Vh8tZwY4UYweZ4g?$Q$hqNbMA6cr>RwzPYa-c3Dh;)KyN|Fi;FwF z(zZ&GtS<15I8ExlzTxo>`6BER2kJd9JUBg&veA+(D9(%E_ec$=e3m|;3S<#ka2F6BTp@tCT-lDo;9h#2!DuP8B6MnSJdy?FWYog%3Eud1i&Z?!36U&N6e}>M zxQlfexdgiP788+6w`mJ{+iWDBO(382H`V}^SOgQpYiZ7K*5#a#;DbT9rU$cK(Xfcb zMHJhrGu9bSf0u4Rkb=Z}ngES|BSqhn3a$%sUN4cl3OjD&0MXx<^`LacZMocvTY*PqS>Y1_#=P3Yj04*SE40?-tQQuNQG9-a!R0%7nW&m-)FC{X; z(GXAiRUUDHAC~`z_X9r7J#a($_2t#o)yzTFS@Zuof%qVNF5J3g<1jTQRsE$;F&Er- zyxGB(*Q{n@r2*O~;ST`LJa&fnEi9;!XM5?u(q-brO=k)#v#Z%e_WGs1pM3K6@nhos z`)|`bzTum9`_$)MHVw>lQfSvv`Tv{E0KfA}!%eElmxO z7|u@wRY^-hfnQ{!Rj;fzhKtcL*Uf^gE$SgXH2lzjAhI<~_B^tdOV6y#74!rO z%3e&hgqy-=pJq(L2OZB=W{j>J$E*tMY;wd@cIK;hUy;?*K(dwp9SnHBT%|M}vR9|Z zPFJpBa@Ao~KKfm9`5?b``>3hUGlm-mkPo3~`H1YhxZ zx#rT4fup(Rg3AF3duAn{ua`x{)Ku{MXEDKWrghd{U4J>frV~whj|dd6zkWfz8v<^- z>P}%-OhmAqQsZUVKmp?`aoj5CL1YpDa&7|B zl=b6nD{Q0PC(tgpMjc=o8g2#@W5z+~K>+^Z+8QHvlf>7Pf}tTIJ3M~v?d^U^NKi(V zESPu@;A;>rc0CxdtufdL%yk6!YCdBF>wTj~td;U~eV z#`Y?@nQL#@JV^Cy;zk&`yPx@I%HKk4;*{B7rqjR7#qmSyy)IZ90KgarM}|j{iI4Du zZ~P>C`#yX8@c+Db?)LSocX#aX7?`%**=%e4@men{p_rB{h)R+PNp)9Z+s{z}KuBtZ zW$EQDaZ+Uy@!4cjXPiOSeGoz9u_HnK11niA&aUrN9CHCwvbbb44R+Yz(XliQ&$~`F zW6?LOPynu$sWj!wMpW3eT-{>Kt|_G}-t}!pH+aiGJajpU(8Q!~4VLYYPvpij8HiP} zLYhBSX`cXwSVTiIH`g$&48Jf?xuQ6HUSe1FXVTC_!8~ar!=an~k9031E>~%P7;aYC zk2}Y*)NOL*kaF%$hIP0O0%0|L>x~{}PiuRv){iX5D9l`vRu=OeNsO@yrpzN(wL+v} zx=?Y2cyD0+crUD@__m;$`9Egy!u2B3r^M!NTm6&z>yqD$zu48ZeB1Y9Jt^)|MPgDO zVXa})n0HWa;{KJ%+&09(-sQ7` z^L$XgoZX(4<Hz{2hK2zi9xgC<`JKlSYtlVmiV9>l1V9Ah;OV|!cQ#N$h z4CFnKsURHjl0Hdp)f;}%>R2u(D|Cwa=Kr~v^L+2Jf zLonA!0RYBJQuHRs0aC^f%MbA&05lzsAL^ca;Br3Ab{FT4W;rn07wDo^lX7aYCmfAw z#{>4cH9q%@hZq4oI82Qn%iOU24N@uN^zD0nmjfl~nfxtkb5u1Q`s+FK1GWbPm(iPe z97CUxiDN&Rn^rdG_5RWK+@0>YyZidpDSdM`! z1-rbXv6i$JsQsQq#7j~qU`fQ=h6h^?FxvZB0#}IrTB@=N@Dk1-Sc_8$5!Nk1aYNoc z5spzj0v0wb7u|-60>p}}ZSHROjH(v1c^g;DFgf=YAKW_UBt6#9xAU{~6(SwOuHkrBoQv-=haFekjv-tvB6fHB9o`mkQpJ}7QX4lxqD~>^Vx2lH zOH?(lIofgesYb=NtUJY{^6p1Wt!UEuKGIuZfz{Z2rT#7Y3su~kNd6o1|E2$f-}Ym} zvZ{t!VwOYV*-YM7BAcVFp!z-~X0~i=wIT`fw-gYn<(970a0~!Eg5e<_iCDb5jqA;~_DA|D!+ByUS-D&I$dxMaI`x>E@GvG6Dz=Bhg z5rDVEt|Fmj^Rz-ya{_j*8Z}@oX&vA#ap|m9fKZQ_u(~X*G%#GN-i8eIVjt_y8g7 z6v!b(qGMnLi+i_W^ub!@UPSj=c2Y`r#7>t{z_L1B~g3Ie$IwCy_>ZK*3SsPHqLS`~?nCwYudp=xnkqs2g4Gpsg>{LnNT<{JB2mj{EciOln< z{txtrM_J1wH}ZJ|7MBvMLoH;|L0~sf7>gIB3i8Yllo3>FYdXbw+^pw`0&4sEFRxik zWlDKTHB?VdHw#FyezTU+7qp?h%9y%KcdZ5rZG=oIqWU0{QM03yPLrkRKN?mB8X_e_toI<1(& zv=W3&6h|MU9U|{&&}2ME+9-*eVP?r-3<%9o2I_W-B)c8eYPV3%ItieX z?d%l?V(2to*vQWCBh9kFtIj=P6Ehfp%%I&ClmIAbSy)-uVX|*lB-~qLs_Pd%4J@Rq zl-IUp?gN9>0qy=<-|An1C$I3q=Vtuz`rEgF9d28Kn2eZ`u$=gEZ)uja!-}>M3}X$9 zFv96gWdNk_nzjMwf80{nSD1>`y!gd%DFD*HJ@@F)b2*gj(S=;ATOG>yGHI%tXJ#I> zTL5*t-vPj$_u%)fe%Hc606TWtjREEYYp?(CVedWB9Ap<`Ucc^3ARQ@1B1b-~g^mM^ z0b?ET5tp&!%zb1o629On4Cc`FK-!e1+7&YlGRMLLU}At^PlRDfgv-rB5H+Gw05O#a zfDOc0_302;WdIHMyz_u5+g)I0^j-?vf`Q`v5Bu`175|EaRn9HID=zs81|Lxed-u53 z3UINq07VrlWVSpFD!Cgn3qf|Qn&x8)sg>jG<37N^6lYMbHKD1Sn1*JPR8UUGi?(vA zBN+0D$1V~*yi!5UrjDdqo>-eyr@C|P^B280NVu4?$4;2Zwf9%z>Ir&aSa2EmT;Qf~ zT~whE;X;|AYHn`Ev*IM47QKx6H2idHM|e)wjt0Z7pxRT{sM<@;NP9A4Ez+;6vtJ%B zkC;mIEv_pb@9mt6iFD>JF`W_9&HiK`)+m(nKq(?zsz+y>ADc5G_)hDz$`xp}R1|*fwm#t5@&=;iu<%&aQr7g_VjOomhjE zTuH1G8Yzek_z0qcrm9f_+L)F%DbYk}pV{-`Ci#@mDeU%So!p)?0=rC&v4)UjJd_;> zfulWKH3$a$FbD|R5M*+NX4e`STj@c%sz=C#ShzbVrQMto@5M%wuZO!L|wvR7$2veAZGo(}A?#B{?fIECR;` zZRdyKOcP!Xk%<*PK0;YQPhHaox3t6=6}ovhGMprbUZ4mQSRH_^ASJx&`}k;YeM%qv z#YJ{u`d1r(bE1FxWmBiWkG78uLX47dhvA+Raa5sa2})QivIceK=-*I%)+0bEc(j|f z(0DchQ17q1d2Xo%-Vnft^V|UjVp~8t4c9(gQ;KNtyK+D485hI&Z&!}-xQ(K_kHmOn?U~&eS(Z5jZw9+ zT|)Cc7%7M~tf+9yP6^lUS=@}51C&e5O3l1~AJum57uJ}HqMJ>@`uOlK`HOoM@j>LK zC(<^9YC3UaakgaEr4-UO1B%ceBc&Krv5;fW!*&IHoL5nzkD<#TL`v97Bw>!-%p9#E zEy{gVDFNhKyuy7I&xrR*9`B()1^>u$($ zZTCvJ6m;!yYy^1#+O>v(NkS6d#u&Sh2v3IIV9tmRT8e;pGE<-jBI~@!BU#h%li=}s zLX))yhk3yoI7Or#I@Skbt+1|eb~OVoKwz($I->?3vrTV7TB?px~Bfoc=z>%8ZOKBBE@*&|ZimA_F22 zN9iNJiWO9-joA0^&$I0|hNp|GkcuLo_P1yhF$NclOd{CaU>*uz^T*D^+pxzP3C2`* zMDkEneGJ${sG-e)#ZW|mWvv!0=&#wM=G>3v|?(*XNr#UWeYUHL3M>7^Pv5( zBMLsWdN6-7oQf3uLvYW}M&~!$Q87roTKLg){!LzWX)zcMLcLF!@SCMPxdk0SYh(0y zd;!%+beU(j_llNY#vkk&a~i5rqUf8#$Av#`tPR&(6Il>>@`c6Btr#59EP=p5w|ZB= z9jKiE+o}$!JFDY|;UsfrTxst@qg5|t8ooG;EqYt z=-bfavQhy){%KCpzdo+Y=Z;TIt>GYQ72x#=V9x3uzGJ2GvW&oM*MVP(&#mUoN zL#Z9()rb;7BhxY-YA}X22xt{L7Vdy%Mv=A)#xv*I_Rk#c4_$R0 zhb1}t%oZTpc=$GufFAx-2Kl9SL+R)mfXSuG1#(kCl+6a^)RodgXTHBw)pSB@$Z>M^ zS9%d|8_+ac^->#BK;>BsdUKz-O~Bj-2b>PQN=s&~rUN181Rl|zU~JSb+L+fp071^2 zrrI$aF_41VmObj1?ZbjJt5Fk+?zr9&fXaZyb7HW{mHz1@R+%Xe|E%~hF=b78Ukzfo z4A>9bV*ut`aVhbZiZ2eQU}U7L9Cp}{W!*s@sKXW-wtPh@v;kM=@RcgagFt`W_ulJgXi51HH50EI7@WZQF(l8w_P`hYXZWDtJ z;X~UEE^DSmaQM3ObYC!v&nJ}9zRdDfc2)En7o*>>Y**m8cZ~?Iil|I%a;}f@4pXkd zX(2IqDwViC;f$y?IXgnfMJaeg@a#-dmm=us0h2Vwfa~a|q|x#k3wZ6{xwWZKZgkcX zl5R%ns#?k~A5#?iuAL7{twdPL>!2?7(kxA0TT&y9%YY4405A6%(Hl|eTOye!0R^&X z3T2Zh1>d!o^YSC8*t;6WvXs6x061H&=}Jf|IVTEnYn+armGX>6Q|w5910g~y(ja+% zjNpKw(bRKqZL-{_eaMQV;3Bx)qxDlpR)Y;R?3gwNExV<6O70mD$zy-cLpTpo63ARR zhwqUEv3c&Zv2ET`(IKcD(AH%HkUYF;K#w4l#vHhvp#iBj#?#PAaNqruZSv{?rc<*V zq;%5y zOhl)qLFb-8buKKL2K01(uOetNVhvat&VcsIbe!FxlLVOt^iWCCvan!{F94L%) z%oliBIaOor0l@A+d9;n*lc0lfuO0f1mbEBT2qM!!0N`)iBqEVE%GoQJ3+nvT>%2dB=Gl)dw zdc(C$EILr`kMwVm6-wp4lG_0@?g&&OP$ z&N6C|^h9bS5WIVIb0&lxSaCzmEv@x$+y&r?YgH=6XHe1MF-U470wqJm-80%dxfeOG zr$cW8*wbtBJB<#CP_!Eh#BBt4wqaC$r2pzbd z9MsHtRAN!HLonZQA%)< z6*#{ToS$r#0;FUUR0C-nrmG+6UrMc1X12iE>;_4H1lmt(waTuZnsSYqB z$84$45R{;4$4R?gfgrScRt^Nf=p!y*E?o)n6p4^mwB{tp4UCI1lg6%!xtg~-85NfN z6{g;4JVmn=5LgT2)mq3&ipXFs{zw44EkGw2X;9#KALE%7~{^IJDcmMTnRtHWEnFx6NTnLL%U|I_HC1Tr; z80QXClHnyBr1+u&v8$KD#;z>ck%kdFVBN_y@vKWGZEGy+yZGW)!3WQuj z^x$!DCgi|gde8|}&;)?w0^6DaJ+-wkrKD-{h9y&pM$vGg$SV{9%+ihw%8R^cDoPN> zhhV|yhoU6`w>NJ3C{|@D{P~BwwGb+6VW2xz}o1=vN@-SWSTaRGO2-A$jn@Y zL3zjpJ4)3F3e)v+kaiS?tC$Ab;;_jkO!xsL4GNd^+T=3%Q)?23&=dvG%VyruATS?V z_w~VbO7%m+yVqp?WYwI+sfWlMhlNnqugN6^J1gWSc5Kt1gR=z#CoYV#D)3i z`Y<#CslKt&C}>7yF1%(6E$_V*k?ZoVNOK$%$CEX@G`+9>)2tHGbR;1$n}@A+YDR8% zb+234y@zi0_~vL&24c6S2)mdq$vP;V0g=&gI*`+$MswTZ#T}g00lDyL4QQ)N1^E*2 zXNG}f8*aMn72rFb<}56FT044j+bCwIE|5Dbhp~+k4%c%fAZTh9+E8`aa*)jI4P-aQ zOP2Qv#aNASE4(uTjEMrtT5g@(2z15NRo;pcO$PF!bKH3ZAyDx!4oW#fZ*McyAY#u% zmx}A`{OD|KXC8?Pb^{M9G?PdgQwvuTU5oBa?zO) zWRr&~PG)A(ky>0HM^Bn(oAep*JiCI`G2m3^Xxp_WA;58Thl2po_O>a)s&Av2fF*G? z7+Hg4J6Ux=CVTMzekhIu3RQ3cl`RCW>8c$Rs~V7V@z!qntFsTsSIVjQvRmbo&=teu z7j(~s{8BCxcH3x4X=qy$PPpdcr%^}6&arcjjg$j6^ou@5Xo3>gIooFJrJHCv_8F_+ zpVeO%{<{2?D?OeG3~a}@8@H+z$Z7vX3E;kBB=P$`0KholGN6Kr4qUx12m**H5!t$J z+csk`qYo~+?oyP|hR#+S)ef6wBpL;n>6Y%2OW(+i8?IC4(YLBafa^%UMIVVCzF@AD z<}wB)v;bQ@aYofOp~!H=YJdc|AdqJ!Cr_U+B#MD2(}IPvgd>^C5mYFGmCUsYYe%GH z=1_4^4^)I(#dUsMlmW8Z`Ts$471v1cTRQp!U{a?eQ}yc>`SqODl}xyUMnoMZDm0! zTb+AvlQHz(Ixwxqb`XL_(X|sYX*%)L7z<+!&(}dnOQeT&y0? z2rAoyc9MqL`U|2(>IY#<2Uu$305VW)$R$Rw;W+SBPw!wFK)S?^UI15z0P>9~HMtxL zH*dfz0cYKyCud*Lm2xyui7?;IEY*$!$)u=0En5LVn70dE#Y~+uuIP&=}bVY(I#t9j?5$wk#cv3tJGH>>gynmGh?bOvCLG9qbUFh@zUHQisa6PE!lJ2NE#Y>*(hZWd(2&c2q(ccOsY0o9u>( z!~mQ~r(zf1wMr2QB4fx*!M)0msHTM*LwKNcy|JLCq4{O$qN34ZXaj3%bCCJuWL6eB z7Apte{;Rq$cZLvgh&(;K5^^wK#M%8KrrtB#!l?{}%h?Z|M7}M2Dae^RuQ~yg5Z}c~n9MmdE zuIh(u+;<9_BB6w9Kn(m|%_?;Yz|Tuo%J;k0+03W#B|iA$=>SDW+*vxNI9s18T1GIY z29w5^reK~+78Z<_S7qd^vJyM5`Wyl?QyrChdqYd@Ab3#5G(^f zY2E<_pere|y;|8i$_ZU=uw(2P5Y}w6WP{Cjo?d?F2 zAOKR9aNCr`cPj>b65rS2&?@b$NR52464X&$&4_r+wyejMQ||EXamk5_@qDiI39br6 zV2BGTO?>6Jk)S;>ZBs#rWvaUYHq6^=7IX!=nlzhGg0MKac*Vh!DMBr@SVU#Uz(wtx zvqGeiOar7!n~WBKl%!8?;ezX`i5l$8s)_=l=ZnUskteoy-^p7-QA))OI+g77V7kU8 z^!xJ3eF}1tv6Tv~5xcIU15GNdCN#C4O4_L$jS8n9JM!m(k|3T~A%dVJtcqW4rDiYT zVqg?KEnz{9{HctqxP7Bay!goU_E&v;;v_8R?OEi*~DX=Ah7$^sX6W)<$H;f=x z;9TvLvNVnxw|)n>e(0UHvD+0=H9i`Xt0qe>n&d4;ThJ`0^|Yoe@|o&T3VBQFWJR2I z*9djMWQMU8)x;ro9)0iDXT2G1G~?dW6QKHQtKj48EeJ}M?e|ub&)`l$rqIX#p}LJN z;`1slr?ZuZhyhxu2+&TWU~Ev5n#3?5Df0!}KoPZo8AfKparF+>qs;_%^{P@t`-&)c zGaW+!5n&~CN7ye7K|(BZCEtR-M!kksd~vWf^)S|rwS;`kLaQio2f4Xz02J_sfY5x$IE`AX)dkj`@N2Z;>4J=4 zL94*m{*t@^0Y&j`i1^A$A%RLM<#NF?eaLYfV8App?;B6!@d!uxVj(nKr$W`kfJzIm z9pPG6_Oukx#66zk4BUwy44woBA}V_tS}20A3l%(uPwXTI6~%A*EQh=BVD>{6uf+fs z0M1BkAv}y_6dc4A4AVLk>4gy*_P-uO~ z+L7ycdvg5XVWnJ!wV`1v3X01{9bQCvTXo2u2g&q`Cp z8l)8?J_`j`2bYP=-=zV3a4HIiIAgY3>s%7JlYLt5w=lS2F3ZBLRx~Xgei+t${KqwAQ-b8 zNW;wPfqdmkUdw1fSQA2NAWk5e5wxZ`ss7&U&2{hB*`Q-3;8~!t0^W=Pa{2w0p|s|n zy1;8P2*3?$D%Xa`G~!M{moilQDZA?8j$fFw`nMv!cpT^Oy{Xkk< zG~1>&kf%i^IbzOX7DF2eH)lyB=CZF;J;ecsQpT}xMS&y~RTXm-&5?ArQLr6uuc}nF zv@0ZQ2(2)EXsFI^y*utIC3o87Go4?-l!uSW6!pHAaS2$oC>Qkj5eD2g&ODPmEkF;a zRp4PD)(Wtpu6hF_Bb4_EYVB!|?p_3Ifz9DTq%7W73qIPz8-awTESo0)Tj) z1vnYY7>u*4ca{r%NLCy?97nCCta)tb?`SU@5mwe;hRk-H7v3he;Qgoh{Q(2$IWymO!&F$Gtpcn$6JAAa!rT_e$+#8OiA8W0yL0V? zWYgVZ@+&BuAq0O;mt~Uq;dtSQ57n^QD=RXM%xp1oBn8L5=}xCiG8ps`=fBwY(zg_=|bf`-!{&8 z1K$=0Q&QWQ?iuZox%lS1!`v%0TU%Q8#*2%OGugV;?B4$}wuFETKByEz=AlQdQOO83 za0j83JItncSFN)CMAA-lz?A6KpI@bI_;^>Y=gC3XY9k2^*1N2_ymeFrj`nsCh#L37 zzpd<+`+zmx?p@(9Ff>G>wH5U?5P2V(6hxv>P%)LMwk3VNcnH%JiglPt2C;TS*R;*c z7ZecagqV0WcbVDu1Tr~ZDHYEV%AqfK>s{lRKklO;9LM2#wW6RUQB*r}`^U`uA))K=u?XG?SY6Q!SC zFZUDPPmS*9A=Jy8gM*}Ak)s`1HbZrhfq(-Cv|DPiC9ks7L?IIg2$NkzW`KvRUxtJ^ zvL#4LMV^@im(5pNplZ}v$K=C}lE0 zpMs)(qhw;4+krgA7j?A8AeJges;estAL>~5Xo-tEuDs*QE6;yII4ePh-Zl&J^I_$X zq8&gO6YXW!P4utQO_PdsJ}vux|h-0`vWaR zxDkvRgPmZiYB?klazhe{emCM4BF+#7on_W@ZQjO$;6*MKe^FF_Y>|a3Kn3oskicv- zan>X>4wrqMAmScpphjVWu&6ZhXpXli^BF@tC5VA`K8db=0U^dYLC-bwQs!7{kJgfS zzAxprYNV(NyKZAs1aZ!rr#CQ?(=tK4E0ZTxTW7nuza4Jc{DC@0F-|rP)lQReRBoGJ z7RPlJ5Q}JB9IX}HCf-3qB?%4q6Zs&fqY5ONWq+T;QaRFL_+_#w$}VTm#6h6ofOLFW zIqXXaIEl>`5mV1ERS4>I2xfWLb1c^aZ}kzmKyirNGijzJ z4AgQifadBPkqvop?b2)G0o$s}fQA-4M(6pl8TS|r!|LiKqt#@ojYTojNnI}Ph~#is z3Wd}d%9m?nZ3RNORkkPyNDRYTdFH2=c)THzAC1`%^Jtvx? z)(Eljsv3<|Xq`4kKBsqtY)skHV<6e@7zRBJBdh9s6^JR`dTlwM?@2WV2j>`@Y>)C2eLUa zx|T4!HjJIQV#FHTE#I?tF|r{A&D~rm08&YvJ&L=&lBzMvP`z{g88#U!WU*d3%32iz zf4AI$QrO#VUE0m(UIaPc5(Qt&m3jPfMrnwzN{0firDr=^&9#G?_WkaKRvc)Rb{xv~ zS2x&jveotWeVo&M5_f#uZH)tK1^Yhf7|5B4-lnY5z>_Pvh{nvCUNw(XpOiYB2Sv)A z>d@mfBgBwNhyj@q4hMnMMNkGbAsKkpWUB#GlVG;ZPsJ{k3K7r&Z8^`qoUNnanj9gL zZ!vB*8y*~1EtF`(Z$e!Os3e}M?Uh0RdFQoPS0bbnwqAgm$&sNb%@8R7k-e>wCi*UV zabO|2AJvu$(3D4Mok!+|nH4T%Xdb8lXx4BN_KI-=HhZNFBN$Qhyh(N5H^wnk`f}Xz z`WYC^2ClPZ$+&wBIjCu=tH|(GK{-XP6X=NiakI9pw-wM%A+eGpeEKd z+lQ9ao`w&sQ~K;%uGOfc3G&wPX=KaOW!`=~z53vjXG7OIyrD;mHm$}a!vKP5vW^0O zYG@&tA_5e1R*F33Q7Diggn=oLFs;$jj$Vhe@BlLTXtO8>3YoK`hQh-0P%~cz6uBBf z43bHc0fZ+xN_!N8V_80D=Q(iBl@{~}+DNKnB^!+`#+yX8W8>)tV*w}y-m0Xz-dm7k z1e!qX6%Uim*AHwaR0wmE01pO_m=d1IAe(n>a20pP;GUz+WM%lREcn%rcb^vaocML~ zq?_)Xofx6(v~Z9cIz5A)nBmyT2{oukVajyKr1N5`GpNjB-Nsxu}TF~N}C#;8@Q`N999w;EEuJBL|TWM zBNB3yP=ijlKy%H-Vsgn_diXw^VOC_HPC4%Fsg6f{q$7#Sv-jhCFQq4UcU& z0z}fW#MG1|v`gr)m1ZcH6Y#`fol@zp!1oiF9*ZM06nQZ5fH}Y>V~a+MCXH2+0wlh? zgZ<~80$4iEB$lznWfjclx>XAU!rD^WFc_{&lOT#Ci<1W3gSm;Q$OpL!^@vgm5(9q8 z3d;~mU`d2BVTlm#AYmA}t#{2pKe25a{>E25_ILjqTFuRJBejHO%=wTA`2zR{74>8p zKETs@m$$^tf=_p%{ z%L+X(F-sMd@TXCCvM66J?IpPsUh-ja%N+AwWb2=04!Z?!$S=VcD|y^PA6=ic;mo}e zoASH0g>4V2*aou~tLp8+bj=EBeRXT{F6t_N@lA3w3Q;)gK_&mj*dM<+7^Ki#C2{SI z8{+gFPD*y^g~OEd38s9^#RD+tU8z%XI;5e&Et@v%kgbkHOXS<*>6=HBbGoI>b>ci( zc5c`r7=|H{j5G?*hl`9-tFQCkx*Nuv+dJh=ZI7l^oLc00cea*#<&0v(5%uP$QQH_6;o)IiIQnx#802UDBZB@M)0UW!!ZUAx|?V-Vfsr z1Yw{q$Y;d4l!dvlE8I?)P>lrK0OR2-<NHQa#$t`L5kt`7CG}-~IM_Ob|d5tM7WCF2T?e#a$$29p^ixey{z2(~MksU^&N?T~>< z3gvEBlDMdhC+cL4*tlco97`W+W8Nc8Z@vl2@Vw^%VDtI7hyju|2A;+Fjn0h_NK*9g zUP$JIiJ3JD8lvKOH^4y=pCA73DCv-mKt~)@Qe*8Q6;gr5Y(NU7XL=rD`Heg~X8FO{ z+nJV>%2EK>A8M^_Z8nYt>(M?SR1uS<2tg3Y4WGQ;6dz|AZ824E>6awv;Ylu~m}BcZ zJvXbZLTGY>&Z4js)vTT<8E#+2d_6xE4}b4APwpiil~iaa7a>6WuFQcOf)?WC#Dt6- zIFgpLUWziFgk~d0JrtKP#6fn0*0_`k{bK%wowSqD^D%K81@sy%wLz5m<;wf_5MF}R`G5s*uQwW83 zlXJKCP;I4s>=k!EM%;6~bdcYleD)VvIcH!W@W=TT>5=EQtn{%8^ZWi7; z@7u`9V|uFtYPsh$WiyW*(e28S9_^C%sLX_0{azhWm(d*?!H#IQF1O0Ikf3R>dD!!> zP^YdR_ZAO0>nwPV_X$(2dBM@t1t_aTK9o}IDdZ*9%$b&hHwd@@TJKlnsV{3b5kY|C zFc72_SM!=F%mcqjM4pJ0Jm-fLqi5pFSf*yQ@vdhB&7@6_TpL>w4o%FONZhjFPzNrw zD&){sT0DAFDjTFR8rHsQxJy7t#GHd4Q?ki{<_c8^m@^U*Wlk~(-n9x4c2+nuOw#8U znzzwjL$Qf*DfrHNl2*P^ZgLwi*9j3|x)31Rotk)}+(`qJG0N6XY|i)A&b4tU#CLmq zD2in^fL?q)1E{oxAxMbjQJG>-a{xO)#J^Wl6uQU#k|i@7e9BsvGLuXf?ImBTcdJ>G ztckiSx49E3C_rx^h~7_*@V5k*GqYcKW!OSUWM1V2%9yv5y}C7R7!$f;XEE;~7%9c` zd4BhmP0}5L&6|w4@T7X~_XNO2y_?8-U)X}1+ISsqWB}<7fNAAH^q>ew6X)0fXbJGl zfZ#LUM@$=L4DMExJ zM2?6?n;kqHOk^e8Q(_=eDdS>f%N>_XyL?g>lmM!k5|^d}t^pV1nLPISr4!Ia(YwIfpl9%CUbv*udAHq22)J=BD0`VsiA z)!(4gC|4zjf#w>CY3Sejn>Exjr>rk6-v)UZN7)n-ClG;-d$JB6a?)&(igzgmAY<4X zx}7TYtvFOM4V>eZ{?Hx1VVPCoP4_qTfY0OIgo`{6QC%uvVNSBGsPkpc^q2l&gs>=> zD+sKkW6iVJb#rAKg+>sfOQjU*d}2{}aze%c`LPmX9p?Nz%sdW^hN=x=7Z$6%W6%N* z615oQiPB}E*|jHP_T(H7(HM;_Xumrn1aIR2)L+uIV`IhBUTjr@poa$TWO6LID!8S; zOg>jlPC#xz+z<;jeoF8wmKA}G0k<%U!o*v*NPb+h=wJz*tiSF#_0zmkr{>Lcryx%d z2`c{TI*Kr$MY3!FPMNSjBHPu}XQg(@|$8A25>I zh8Dhq;6iG|Dy$U5c#n}NE{<{f=*8{7J;QeyPNyc)>I%d`fDSPX3hk6Z3P`CAy1f<) zV)^x4jI>8JLpm*Gn>I6<=AN|~UKN5C$1PNjB#81s=Cnbn+~_biRUsV*kf>y#M=yJO z1VKBP5;JfNw8T;LGEparqo0khcO06e;fOY4O7P$&0Tm@7tTrIEjH%FK!qBn64=KFw z5tF2g^HL1&L@#z$xU)(E?SsM4?lesf(BlcpDj=*`g3>21ro6q)ZZ#+Q5+^9zs_c~k z7>0~S9@aqvnUD5UZDA7%tzPXwYS#X~$Rm`n*Mvkt-!=R)N%p#|d}Ntu#oV-`!0UlF zn{*sV6_@?X-HQ#M{QteGvU-9)N;Q%v3Pcz<%u^GI@$^Ygd074u8cl~(Mg8=Kv2vON zbC6Y0jj|}bFS`-H8B|W^`=5DsZ(eTTLDLPUlk!EOYqX{jB=FR3f;uLx**H_9Wh1I$ z#z-TX-kt~5F3H}if@bZ5bjfrm4r6;Jsy?jK0v)I%TL(cI8xvD9mO?U)iwGe3`MqfL zu^)TmFa+E&z`MKbHTL3g>cuyZOzf~XI^S)_gaWHr2ccot2~CRVD+^&|Rc? zNAy+i;;Iv4G%1C!%4}st+XW;Wu|r#Iv{HO%Vf%vwQ_bku=<8>?}4n(1wqI)d3`6 zvGK0BR&ys0TYy5XZyL4nDIhnpa-!s=Ye|;tJIREEkN)GaMT2%(sD-sDs{(;}Q`Th_ z7Em&rbp;;P+v

=%qA7p*0w9TA6b839!v1)j_P?>j~kQbVOpErm{ZPV&tr%=`3kwRl#Hz4mDOB zyo4`4e}XlZ_MBY6v;bx1cu{~$P?|KoMnDUsE>&AkqRtmwJQ|U4+&#Iwzmjin-hje@ zq8tBy_KHKdp5dfT*Voelz?g&Wg)6W_7XU%nLL_!%DsNGr+>f+M5K_IntWGXapO}bQ ztpMKouMj9qgiNC*2ml`qH3*~u9?aBhX|;NW4mbz|gYK~w^m9GPz>-i5Ll_S~%)`9o zVE{XBqG`2sjEZtjNIw9z+fxoWdH5?z$xgn#%Uy0&rL*_s9YPU}MVhwtU|O;$S%OddBcpcN1*zvVoa$y~rK^&S z%`FbsWzHn#ADfMb^VZHz5S0pQtxp)%KT!xC%5FVU3E>wI5*RBzn`ijSX%~g)cFB-h zbeHsb|6Wbc3d8AV#Ab+ERN_%Q$Medjwyu9ZtBVRs0uBSCcox=T%lOMX#>W)WQTiY= zELb!gsUFU7p|;#&mNdp#zvDniRd6Im96ZJ*=tdT_7{>xXrmfUnpN~Bq?Q{e^2bHdRxvQiN#QVw?>-{r4bh@QmP2_Ds@1OIxACD2RJ?IbsgESnAy?J2 ztYGHW*+4_v2>`(jks)p$`{x)y*6c-TI$2Bt6ykz&zF_i9VJMywFL`p3XdLS{PPkvu zKU)tAdJo$9#ypv{2aIAB35QC#tq<*~*Fx7FpJ0!i+qg+2z_29=F!GwP$xc%htrj(` z*;HW|f@(oU1xP88_T9TW`objQ9$C&0AWFudmnF;N5!GY$s1Ew%tHC4H6k9H>hKU1T zErCf>k{8NQuwWR>CD)u;GK@LL09`<$zlt}sK5Qu&!T@*_i+>b8CmcN~^L2$dki!M{ zHm#*6c3CbbtHKTdTY-G2@^Ys0VsHXBIVhfTph>y)uAjUA@}J{}KClM&J>Py}MkE-A zYAsC+POf|z(11L6i3qcFsWgh`84IHnC7tMy>*E|~0^jMQ6j=bGpAXyNm=+AsG>Hx2wv<~xY75NH)6upG-V+z>AX+sCGMi9CFs*0 z$cA>GW7%^l5z2ajk;S~Ay0yARch0o(b*|8O+5`ii={dPK~`ET#+-&TDO-zY zH0LwcN|0)=u%>K%w+%%*2D1l8L+LoDl93=|2 z_FIx2$?}ePlB7!`AUbwbrxH}6Nhf!Aiw)jW9Gho4bLs8?;K_*CTkYAvTELD6{K4-8 z+ry44q@uy?+!1->H-Fi=m`a%5Z19 zr~33ZI%zd_<-95m$;n}bZG|@n-ypHFTShJ=l9+&+ZM%vrlv(1G<7x{0laWW7148M> zOP-*NEuFu1d{bUX1jmS|dUn6th13gXYJ25LSSRLeKFx~x<_6y(L#HCbzM4TjLe^kK zgEk4K6#05;7YjTTHqn$>I#`R^b&_5&7o-b${=%8~O8jk)j)Hrqi?<(jgG~B54TzVr z)|8#z$&&{UmvoA7KAxl54ZywIVRGuFD(7g|_K%LJojgJTH?={C?QqVm?e!o&u^7ypFbjOdFox{MkzcXPiOwW zQ9~7+_lXbCK#|wDse;Om?4Da;^18=Jwhm@0+Ju1g)#!8r9;mw6i)x#xGm4%}s(jW= zpyC>zy97Wh9JB+X2rjt3`A!tZjG={WnhxE*p&k*K%9wFC*WxrH-|xX)(PQ9y?%G`g z>#5ULmQTh_X~IAY>(Fnq@Gx8g$VeEwlXnp!6C0xvv!TgaqQXWiT}y4VdIpdHb?^D- zCu5U4L=iYR0i+$Z3j3A~Hv!MY@^4N+XICK9J=-d}pRRx7qx#IyhFq%{S7hwZG zO^`y0_8B^k@7#xve(r}hY$9`q9PR+>CXBIQMY$Rirx$gtD;WkOX9nOUhEfCJAdbjB zlrgtJ3{0q@=FR1XTA(=RpqjyCn?BRJj zz+R+QlvkbXeYX8XuvGgTfkpT~3yxO9&vX2iVB!z%E_KblV0E-ah3B9$1YPZD?MRAP zEucesi&@o`f+v77w2f&Oa}o;T(NRjuBJWC4W0K3;Z*d%lwGXE_l-7}_Bv;xSv&Ag8 z={!zT)FLq3OO2d$YXa*yj~HNF?w#qq^_W;AcH){XP>qgE7ai0iS-Om0FiNAoJ$geS z7tx?Wy0|#q<$Zh1U3Z(4)PSb|j4FKI6O|IytT!r(cPlIANv_L0i#29VeH6)oRs`Q* zw-{xu=@yE6vlcQjd#ufeLikw_J-W5|JR@2ek;sNxnaQ!k@9b3OxA)yV^s;5>NRQ~) zXjdVNLf&{i$+O6oTOZ2&EFJSVWzlzw?j@vxMHD`=QX7boTxS?p zM!4;q=iZVaZ})y z!=4MHx-CTu?(nXM)wiT9V-3cc5{;tyv<_xRR+mfWMH$~+rY-?~s*Ofbk)-vF>8gzktEdYHB*aV+BGs(GF}@guwzRr!ab z0JfH{qA<07EQ#W+oM64pj}q_pJ|V4bNVTZF3|i=s5hhrz`XD?EFtrW^06=jK&x<7% z?{R2chQp$3dT|55q*48u5;p9v5smN-6n)OnZzY9cp(2*WI&ZjLsTG7HfmVNNhD;51cb_hnF$Ey8>3=` z8Z`0ZAWxG#a8%OuGIaHnP27VSLBHgl)7wl-3AZf+_%*Xu-$U^Q2OmDvb6+DE!pgDX z(X)W{1bc#Z1mA6)4~QoVdYge{tt)skPk!e>_-%6fT_w@P zVP{|DNE(sFd<513*syqF3Yn?2Lwfxh)EH+nd@2TA>QDBO(6OW!c2B`|i6eGs)DY5PI7_|TMa9(Y3O;f>GO1xi>AqdDTT~K&%O8v@XvGz}^3f{fo!EK&*g9p# zYHQ;|cic|9aT1PAaG}|-f9t|r?A>Dkw5{0bXDwww`2z2G3gYP7tJRBl5;3~*l z=u6B4W^?T%v+xw&;yzLNc&Br;J=12SHpMd91XBOSOgW^)(?U89+;YF`8qZzW0tj4- zQz8nRrZa#2LLI~VhAri;fCO{b3tAp|=~`mvIF9m~Tm zAk!t2JB{WXVuPsCOqw5B;aZQXNy3D-aY-)4g+O0SHi%kuhu}z-29%xxtuJ*Sus;F~ zs-%Zk)AtnCsO=VaD@KAdoss~_CnL~BF3>2qAjp!{sKI`pz!#RI2X$6WUWV=w7V%iN3~INK z?{Na1Z5XJ4A?$D~4v33ZjBOBWaWt$0PFk-UgV+R@3BO^3$gH=CX(`m1JV7w`Y0wdT z_h{E_rE`3Pnsk-b)iHUo36lv5#n96waBQz6IIrjFm!_;H!+q;~@N@UsZZ|_1Ob@Bm zBs6XuQXMr6E&W=eT`2Qsaw?WeqA*TkYg|Dg7=N{836OADdZ5BsDikp0!Fyng;_5&G z59~CnrcvF-?gkOJ?I}}QZ2}6i={F#D_QNs#cP9n+R9oi@vsr|;uumOE__I;;A#6*A zL>7uHN}|+uw4@u>)F@FTLfu%R!r7M!DlnXXQaSS(0!KA4bIJiYY_ZZ-`tO)k!~(BT zsK(4Y*OIKc!#t)CTrLl0wTx3^VRHJ}&~UsS4N{NhC;-5VSh*`W=JRkahjwQGFT_*0 z3E`b^IXalquHqfDIk&BtC-ESkHY#HE6nw~)?fa$cqvak=z2eoE1_8mY#8fN3vK*~5 zF|Ld!cWhUPW}zSHIoHH>)5%X9R4_p~9*=|*an6r5ROz1_A%^L06dwq7LmA>kBok{i zc`fDm&LkmNX}Oq4GMc2WraGZeH#)RWyB4K~%&3J>=+H$y?I5wS15V4#d|9 z85gW~&9tH|eBsw3InyUzYx5eiF3y0|EW#58*rx7I;w+sb+XrJw5+XN`>}cPvh15#m zoZ~uNxhs6pgfx%u*<{$xamv`!5<>iHC7lE5RO1*ohY@QWPq0UW3c4`*#w_kXvzD^6 zl%BeWI=*?Tg)P3g)uiX(daa0GNX*+ZYrcO?M-$Fg#tVQ^fvq&1FAOln z1!0lu&yBvLh*i{pbpf2XZF7F?1FeRA;ToaF0O;*klE0x`sz537P=<{kO6iE>$59cb zQi6HcVbPg3V94GL6K18+RQ$z8j(#<+Mq77{*_3H1@u(8D6)-61Pi{%98b`-*cLENuvz>>G7}~NL-6qKMG`Se!w68t z=3OAQcUstYffGQ2l3+ey)Gkd8GC&j6Ax}7n!Zaa5*ZuLLdfnYZy&S*=kx^G@%ZqK~ zL;5iCdC-rwS}|_XW*2KP+c1Vxib!JsfD<#;>g)1x#TWtUvt|*@CF;ox;Z^Jiz|#hr zjh7qWUHE)pX&o{tDaoQE(%TpL3;=#GvKrmijJ7PBy0syb^p+}RenD3I^Gb$=vRN-? zI4PB}FF=dRmu*DAu7~()UW}S%lNL(-Gm7AwB&(K1ZgI!k9FLNal>j&r({_eHx2ZYZb+O zbLcUh!r4mVW)qC}0Onih8}_8sH&o1UFlQVwX@x(?iM2IMx1qLj&tz;aywb><^44fe zqc#N(atJOR!DVABK!VF{JhOVh25V$pEH{~lW`$$AM22kS%`N2}?epO_wH9&03kqpB zk*qA{4V@XGy3qWDl^Y(x#M!RRXx@DBdI$l4~arp@w^{ zsiTIdZ@d~wLJ<6cK&3Q&d%^Pygl z_n56p@c1K3=Hv=D%F)GtJhr}*dP>sl_Dbu3|2QEr6SD9Oa<#k6?jTB)h zAZr(agh?;3b-RTqF@?|)jI;$_%guha@#J(s8I;;~%Xlznqc%i@U5Cur$fLgBaUShQ zjcH)Fn6@=wqUCeXdcuk^v(#6oY^qsF_`1h8MezvFGTK=||l~_6So)#aCQ^fYwU)a&`^RRXC?0 zNP_z4mvghW*m0)xyy}XQc?)mPxgdS28tbTD?)DRLniLRZ!9)inbAAtV$Ypig!!E8B z{K%Q3wnIG5Z>zDg{YR}?(ixQ7y2%8xfSDu!#2WsbV;i8yq)P@rU{+WAu|iIRQpkz&o`D4rsb=k1yb}Fn(pBKN8JDVl_$Hi-D1s(KfHcz zXl2S(Lk$a5BuyC0yZsC?%ley1i|~|<1^yQwoNQ5JspRE1czZ*yB+wloNfY=Rw&%_p zwK%7Dgfk;xr$b(r#WyN}5<$c}Z_#>?&FP?G1IqZUGSN^2T&Oo^1)Dr(3;qha_ZX+0l48*2a0Z z?XzoQlQDpzk!!U0Jc}zcA)=7;R+7WY&3^Q8LFSD6h)<;jg0A|sL163|P$2RvF7#M- z)R8fqScT}(XY+`c>kv&484Npv-XQ!)tuYBvx!3Jur^}j#Rt;gTac601?_3?%aMF!l z&C8)<+#E0AT&{oS@rZ�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%0DJt^oO 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%0AFo;Ep(J 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

tl-+Y@TtP6yI z=|S&adv9ruFx-IZ%uHWY_de-tLL5WLdkY$Jr*nZX6)Y`k+hA5uhMr3N=TbL!$l5Qd&p%Uw9eMR87$(3(RAb1pW+PNy!#u;75D&CES}?f5Vw3HEC=V7 z%F6jx6&YN=j}GmLUye6jJpwnihZgY892FsQ?hrqY%3nU^d>Nb{xjd8EaP!v3!e0c( z2_o~Hb5z`Jo*w!#n5s@mWHdD1u+2u3Q}p@DDJjU%C6O<1&|BEP0}m}F3qUfx zc@nrUTAhd^bdNNGN)n0b36i+_h%}7t^)fH&{q6Gdwg9tEi1*uA06_GCHnxwbP3Od$ z3dbk^c0i1C3 z37^r=om)~i@vc-v9Adx0Z$Ki=%i6M*?Z$b_#@&`lJ)Urfu%hfd8%$f$!NNShOzT-B z31rA=ef1MTO&PvNVGb!&H-f3?UKi5BoaIaJD%@!xdXWK6Bzu|4X*FGwd&1feKMCg& zTP3~B^D~1%UOsrCbz4$Bj;BqO;%m5tH6D2n!ay`_;4lyCOlcfLUc!o5E#?`6vFP`tR<=ybMap;O|BP)hsdC6ybCj3(P;FOq7fZ2gb6sCXag39?$&*J5 zRih+hCA<&rU=<#E@LRXXkdts)3LPYA?Uft5ioVk+(fRF8KDJ!R92S^;RSCpYKmmr@ zlvzXswqc`y$cy+pYug=O^nL~Di7%7C0sj?>T4XOk2BBK?7Sbn()_WtHWHCtO9sdOM z=;$ickh4G$ig}d`LX}Fjs6WV`vx@!*l3cHLhuY_waJn++rn_&0?|2a~=vtNZBq8q$ z!Oz%bJWI;c^-h|@eW=Yb=m7m_6`Y;6lZ6i^z<=!lS*m|P$*G3%IUSJ0>=-NowEy$Y z1G(N}pky* z$A~jB2(IQZk&^&(LM~MnKGh=%rxu851XTkoXH1067dz6TDT~>5nL}VWNbrynu)K() zG}r&9Nir<=&vgvWWS%9Qp2;a0wvq#FW5ghpR01ZUH{h4D3&O1}R0~gv%}C!oZ=n2M z)^n-=bBwdh>auO%TEqp03*xm~Iby7HT{h1>|Cb|;T}mD=vYfV-H~424l@1J|w(cAx zI=lyKUKzrt z-n_-kAESd|a>dKqL--!*KK=ML3f3<@bRs{9a<^4$=8z6Wf+=3;e)>ua!$@CEUsUaY z!#mY%2Ci4VCWn1^^XXqqV&5b0yVRo%|LN(Nun&_jW zKJ~s5ybR$PpIB++mPgl2K(#s%0y(prHkAdfw^kU?@Py^jS}_(l_nF$3H>5>wKaW7p zvhYNqaf~hzoQa})LuHK|@pSK`fgKpInQcJp*_B^U*4=u83|XMoOHrL9Vy=?uoCLH2 zqq);NY{5wq5>G_XIVC^yPSo(l>r4$#*GEsUCo)y#t~`X(C0l{ znuX~g8iDLHf#9?_Sj)m1+zcO$urNEJ)z(ecaK)SEdZgrM9|RKLc3N&s)l{YHVtM?w z)_sh!D{_eBO)R8m5X~y4_a)96thwcOxCIVIgD=tkz{JOWug|*I#jmqFgcW74rK<~@ z(>8C14yt-ssA(YShF;s~r`2!moW@9)PqKm?SV4qe0hdE#8YoRun{cF0gr!S+&@mw7 zj-y0c_w7Yqk~v{ulZTViL^yK=qu?j=152##d}p$U&11Nn^^pXkMfj{wQq)&xj2vx7 zX!FJ>L^SVmXP%(sc?u#+n}dk5wWBI%sDu(!j%0d5fl0~K((Jbf^kUaA# z0z(qG5!%GA6iE<8Yx+8o)d%LFrvj2BgAz+Ji>(L#MBw*Kw zurNTXoT70G3kJ85S>>t`q)GOZHeVyNG4K@kAK+hs-Uo_c6OojDGDYddPt>~|k5zgx zsuWT)T_-&6RmhLVzm4J{8Ox8PKgGx>c~F;{qDB?;d2_@`qw8yNt41-Ym@o@7xaBN}1w zS*G^tqaj#_O-5=HXcWhIA@Svz3yFanG+`zPQot*Cvl*OGX>zhX9c~)AH-sfM^(2Z) zhXN>?OHa@j%Cy+Jk}uqPgKC8X6S=jVB)?c#WP@BuOzgwk?(tixL0`!)s0-%C)Re^s z2fqA}fv6n5t8=XH?U#J=he=EgAtxz(gYKt*IepEwE)N<0&%_Il4D@@RpTi@WYRrVx z>YgTT9bViLpV5UPFh2F&fPt}^c%!k~4Hj)o_l!)7NePQ)JBv z7PF-dbWCDSlbx``dyV>J6ykn;6|gU$Rag zG^Fi{6J!~Fb)eldl96b-VG#K-(}3{!pn}n4pE%SbZ3y1&4&{#%J^_ki8%@1K?(*3E zG!|A&i|KRf=jXKfc2-UT7OKYNvLyoTZ=$^o=<@C{r4qz}O_BOEdZ?4=n5@h;{&lh4 zH<1BOhs9CNl;G|->A*Uon<8ZRQHvI2;7T&)AYkcu%(h%*oXTI&c|0>xQvOOyu(PcQT;H9ghrS0y}1Y|6<*!x|$fLsb1nc$s;B4&LCsKgla3 z6T_I050sJ0!uz`Up`Qo^H68scYc+3g9jzlF^42>PymxM>DB%AJ%*a#ZYOfTA)61T? z5~xQS%k3Ve22x}S0sWzush~=PXW%s4)QgP{3DFA57u-8yAhfs_a3OXDcP~x|bW2B1 z7(F1)?rEke60ivyJL4Ez2}ipEAyxO5N@~A{uZpK#XpgGE$c7mvsrLZ5kZN_tQT1g`$jV`BfDeQTXmi9Hj5Ip(Jwm^MaK+jX_QNDZ_~cwBK{ zPZ|G4{8{uLhbmOq5y@L`=8;1}wATX)Q#mXT^NmDc)ZL1Eravn`YSc-v9-Wy!X=MneMwxqd16LAz(xYuyrqX zLe$niiVwt`N+v;_QC3x{QbA-<0GnG}wznJf15Ma9SGxuNVb=P~YwBmMthnr{gcWCz zwT08ue@wW84u#XbfrVL$%VT+EW#g^FnhN=t_3Rr`Zj|yW2qn*?X@h4{w{0WTIZjqf zxbs!F5=m|j*f%YXkS9nw4Lc65K(bgvZEuE0yLkNVKPGK5iGt6D%4=saBmyXUKxirY z069?dgxh)ZrO@phDeL~l&;wa0$Z4CprtzhHk_upYn3PjTs0!y=O6y7tBDsw9eM5Tw zma?6m*R6126z$-Eow zCF=|sLx3R0WNR2bY0W|O$p2~uSjxi}e~OEM3bJvYpN4?;ssAfUtOenO{%CFiuuW7b zE;S|_N*6S=2_p9oT-eybpik$qG&?W*7l&V?L;Wv#k^lN(s|yrZt^_Q|3&&3%JIuFl zH?|gF8wfymp@twFM8e#NkmJ-Rfm3(LOK)ka?bMeCcmCpVa$-BfO9)my5Nq8F54oW% zQj1bt5ZvCI7!t88#8|m8Wn;ENV`Aj)PiO#};fXmQ5>TKL&N2O%vUn}=q`>xg#z|D< z3;Rus6m+qZc?gA-P;B8eT!-B)Zk{0%2+aTTBc!PvZo5lB;LT5YcUn8z*(2nOo`{F$ zxS2F-Kc^B4X;VEY7U z)N0Ax*wL^yp>HAXZ{2F7CV73~0FD2ZXZjWwM21ZZlkm)2Mz$_DqXok{ue;Y)?=J)B zO^RN#y0}dK==m58z#FT?_B8A|@Q@yk7nh1{v$B1$-osw$x|5=8}89e z#$wWF=NAbnLgmBY3?8U_oiCA&e9AoClYR|+XlrHVlpN_feT%=IUN+h zp$RyFF`c%UYl3jo=R;Z>w~7Iy0g7JiC>GS9@zl%^BdDqoj_v6KqK9(zy&ZTx#T%x? z2@*s`@kXtazL*w~%pH0D3XM4s#lTRhVl^93A}-!2u)ptk?b+{=NMr3(NV$LR^n*#= z81~^i90`Fs;e`bDAfj`sIQtzdZ=hW0?D@p%7rb8&tZ;^4 zS{UYc+jw_F$2TJ>%+8vxqJzoL((dO&JLHoNoP!rkhJV^NcXJ2Evq}<-Ni zxzE?$*hhDNJm)*rcMq6eIZW3u5Ok~dYQt5&3xrvMB91d-mZrmj4HDNtbLc&G*r*h$ zNJ$G);e&2}qc){v()7o0iGGm(Bus>}C!DtA(J1nTM@w*`Uo&u&T%Rj(rhnP_5$G4J05=2UIUo;n%q)f zCpI#@eguCFJ}kH290bv!f#mPALY*IzTtorR8!LyCAdQ8`n>>c47#Bm|O6fvDTl3R+ zG%d-}Wn=o5C!?|o9jS$Bzmr+>NAyfEF`XYXb6vPbi3^RL1h$JMj?oo}nHQ?<6t+&} zJY3RN=1hU7W4PQ&U%%#LfCT#If%*NTj!rTV`}-V(n!TZv4r?G#umb&Kav|Xqd;rgm z9AY8zN#$H7{1<3h6jjk1hXur65hYX5fLQ(W)WN z%f4eJMYVvZd}iVY?@dWyuRv}A1Dfod0Lht zq|IWI^T8RU2~y^)k~&V2^AuYXj~*K^4Oyf~SfzL~N&**R5;$!=00&uIYNjCu);lUg zuKt;z-e+^f9H0wGnXyDDL4$BwgHeBl2@IN5`^Dn9=BQDlaX|3&{@f7K`+;L>LMaqm zfdUFFQp8pS3@~h1$WoHb(Y2^X8iSttc^JLu{f#8>GY@1UCMY05A{_DsHGwqP1BX<_ z&c@IekFjiU0{VTgp0}>xYr^0s^{_*cF#~Wx8=)L#Pz|cD3{k5U_ci!cz*Ii_SumF7 za~TMZdseA+QNuQ^j`YcyPM~t-TV5R`T+I24R>CLYMaM1V~Ivmq$-u1(AG;A<*47b%-gZcGMK0?g5$f~Wg2`!7e10}szE0_n%Z3* zi&wpQpn5-=9gD$MwJ5Pg;XVOaUKIr}MOpOWpdSIkTJP-wcBgkqwb3h)V~9@S%5!z!P`@4|Q^;69Uc9 z1EM(u)L;<~Kn5*Zn(&EQ#6k9k>cJbzavK6@KkQ92H~pr+iFhjJAc4USk{FG{*8vZ{ z(pZ59j%d8CnftXopFA8Gf?Qy+k rbL;SyJ-mBEj?I>#ms+8lyEhhVXPYzqMNx?h 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%0A99_SFcVB12rjWhZr;f0s)@xQLQR| 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�X>?}}k^AenYxMMK}@An7Ph_{?`cQH*{4K!#bV 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 diff --git a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt index fbd89738aa..0a81c2200c 100644 --- a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt +++ b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt @@ -23,15 +23,6 @@ internal class DefaultPromoRepository( }.getOrNull() } - override suspend fun getTravalaPromoBanner(): PromoBanner? { - return runCatching(dispatchers.io) { - promoResponseConverter.convert( - tangemApi.getPromotionInfo(TRAVALA) - .getOrThrow(), - ) - }.getOrNull() - } - override suspend fun getOkxPromoBanner(): PromoBanner? { return runCatching(dispatchers.io) { promoResponseConverter.convert( @@ -52,7 +43,6 @@ internal class DefaultPromoRepository( private companion object { private const val CHANGELLY_NAME = "changelly" - private const val TRAVALA = "travala" private const val OKX = "okx" private const val RING = "ring" } diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt index ecd647ae73..8a2b5aa1d9 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt @@ -4,7 +4,6 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY -import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_TRAVALA_PROMO_SHOWN_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_RING_PROMO_KEY import com.tangem.datasource.local.preferences.utils.get import com.tangem.datasource.local.preferences.utils.store @@ -42,17 +41,6 @@ class DefaultPromoSettingsRepository( ) } - override fun isReadyToShowWalletTravalaPromo(): Flow { - return appPreferencesStore.get(IS_WALLET_TRAVALA_PROMO_SHOWN_KEY, true) - } - - override suspend fun setNeverToShowWalletTravalaPromo() { - appPreferencesStore.store( - key = IS_WALLET_TRAVALA_PROMO_SHOWN_KEY, - value = false, - ) - } - override fun isReadyToShowRingPromo(userWalletId: UserWalletId): Flow { return combine( flow = appPreferencesStore diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowTravalaPromoWalletUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowTravalaPromoWalletUseCase.kt deleted file mode 100644 index 643e7387c8..0000000000 --- a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowTravalaPromoWalletUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.domain.settings - -import com.tangem.domain.settings.repositories.PromoSettingsRepository -import kotlinx.coroutines.flow.Flow - -class ShouldShowTravalaPromoWalletUseCase(private val promoSettingsRepository: PromoSettingsRepository) { - - operator fun invoke(): Flow = promoSettingsRepository.isReadyToShowWalletTravalaPromo() - - suspend fun neverToShow() = promoSettingsRepository.setNeverToShowWalletTravalaPromo() -} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PromoSettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PromoSettingsRepository.kt index 758763828a..b68a740e09 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PromoSettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PromoSettingsRepository.kt @@ -12,10 +12,6 @@ interface PromoSettingsRepository { suspend fun setNeverToShowTokenSwapPromo() - fun isReadyToShowWalletTravalaPromo(): Flow - - suspend fun setNeverToShowWalletTravalaPromo() - fun isReadyToShowRingPromo(userWalletId: UserWalletId): Flow suspend fun setNeverToShowRingPromo() diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenSwapPromoAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenSwapPromoAnalyticsEvent.kt index 6242d8bbc2..70f2a8632b 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenSwapPromoAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenSwapPromoAnalyticsEvent.kt @@ -37,7 +37,6 @@ sealed class TokenSwapPromoAnalyticsEvent( } enum class ProgramName { - Travala, OKX, Ring, } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt index 3cd5eacc17..39457c1113 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt @@ -6,8 +6,6 @@ interface PromoRepository { suspend fun getChangellyPromoBanner(): PromoBanner? - suspend fun getTravalaPromoBanner(): PromoBanner? - suspend fun getOkxPromoBanner(): PromoBanner? suspend fun getRingPromoBanner(): PromoBanner? diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index 2db549db07..2d434ac248 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -62,10 +62,6 @@ internal interface WalletWarningsClickIntents { fun onCloseRingPromoClick() - fun onTravalaPromoClick(link: String?) - - fun onCloseTravalaPromoClick() - fun onSupportClick() } @@ -87,7 +83,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase, private val shouldShowRingPromoUseCase: ShouldShowRingPromoUseCase, - private val shouldShowTravalaPromoWalletUseCase: ShouldShowTravalaPromoWalletUseCase, private val getCardInfoUseCase: GetCardInfoUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, ) : BaseWalletClickIntents(), WalletWarningsClickIntents { @@ -259,34 +254,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } } - override fun onTravalaPromoClick(link: String?) { - analyticsEventHandler.send( - TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( - source = AnalyticsParam.ScreensSources.Main, - programName = TokenSwapPromoAnalyticsEvent.ProgramName.Travala, - action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Clicked, - ), - ) - link?.let { - viewModelScope.launch(dispatchers.main) { - router.openUrl(link) - } - } - } - - override fun onCloseTravalaPromoClick() { - analyticsEventHandler.send( - TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( - source = AnalyticsParam.ScreensSources.Main, - programName = TokenSwapPromoAnalyticsEvent.ProgramName.Travala, - action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed, - ), - ) - viewModelScope.launch(dispatchers.main) { - shouldShowTravalaPromoWalletUseCase.neverToShow() - } - } - override fun onSupportClick() { val scanResponse = getSelectedUserWallet()?.scanResponse ?: return val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return From a5914f689f90a975bb045e4bd89ea8738011f400 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Nov 2024 13:51:52 +0300 Subject: [PATCH 55/99] Updated on 2026-08-14 --- .../com/tangem/tap/common/DialogManager.kt | 5 +++++ .../walletconnect/WalletConnectAction.kt | 2 ++ .../walletconnect/WalletConnectMiddleware.kt | 6 +++++ .../walletconnect/WalletConnectReducer.kt | 1 + .../redux/walletconnect/WalletConnectState.kt | 1 + core/res/src/main/res/values-de/strings.xml | 21 ++++++++++++++---- core/res/src/main/res/values-es/strings.xml | 7 ++++++ core/res/src/main/res/values-fr/strings.xml | 22 +++++++++++++++++-- core/res/src/main/res/values-ja/strings.xml | 7 ++++++ core/res/src/main/res/values-ru/strings.xml | 4 ++++ .../src/main/res/values-uk-rUA/strings.xml | 10 +++++++++ core/res/src/main/res/values/strings.xml | 4 ++++ .../plugin/configuration/model/AppConfig.kt | 2 +- 13 files changed, 85 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 8bc1f8e7ae..c48a2988f8 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -140,6 +140,11 @@ class DialogManager : StoreSubscriber { message = state.dialog.error.message, context = context, ) + is WalletConnectDialog.UnsupportedWcVersion -> SimpleAlertDialog.create( + titleRes = R.string.common_error, + messageRes = R.string.unsupported_wc_version, + context = context, + ) is BackupDialog.AttestationFailed -> AttestationFailedDialog.create(context) is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context) is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 06591c01b0..476bcd288d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -37,5 +37,7 @@ sealed class WalletConnectAction : Action { data class PerformRequestedAction(val sessionRequest: WcPreparedRequest) : WalletConnectAction() data class PairConnectErrorAction(val throwable: Throwable) : WalletConnectAction() + + data object UnsupportedDappRequest : WalletConnectAction() //endregion WalletConnect 2.0 } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index facfd60eab..fd477038f6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -78,6 +78,12 @@ class WalletConnectMiddleware { val index = action.wcUri.indexOf("@") when (action.wcUri[index + 1]) { '2' -> walletConnectRepository.pair(uri = action.wcUri) + '1' -> { + store.dispatchOnMain(WalletConnectAction.UnsupportedDappRequest) + store.dispatchOnMain( + GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedWcVersion), + ) + } } } is WalletConnectAction.RejectRequest -> { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt index a7640ed758..1cf6c4da24 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt @@ -15,6 +15,7 @@ object WalletConnectReducer { is WalletConnectAction.SessionEstablished, is WalletConnectAction.SessionRejected, is WalletConnectAction.PairConnectErrorAction, + is WalletConnectAction.UnsupportedDappRequest, -> state.copy(loading = false) is WalletConnectAction.SessionListUpdated -> state.copy( wc2Sessions = action.sessions, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index d1a9368288..d557aa6910 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -82,6 +82,7 @@ data class WalletForSession( } sealed class WalletConnectDialog : StateDialog { + data object UnsupportedWcVersion : WalletConnectDialog() data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog() data object UnsupportedCard : WalletConnectDialog() data class UnsupportedNetwork(val networks: List? = null) : WalletConnectDialog() diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 8fe310b7d9..d5b6d3041c 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -180,6 +180,7 @@ Nutzungsbedingungen Heute Transaktion fehlgeschlagen + Transaktionsstatus Transaktionen Überweisung Ich verstehe @@ -187,6 +188,7 @@ Nicht erreichbar staking beenden woche + mit Ja Vertragsadresse kopiert! Verfügbare Netzwerke @@ -292,6 +294,9 @@ Für dieses Paar nicht verfügbar Erlaubnis erforderlich Empfohlen + Gekauft %s + Kauf %s + Kauf %s... Keine Token gefunden. Bitte versuche eine andere Anfrage ID: %s Transaktions-ID kopiert @@ -575,6 +580,7 @@ Residenz Bitte wähle das richtige Land aus, um korrekte Zahlungsoptionen und Dienstleistungen zu gewährleisten. Einstellungen + Du kannst den Transaktionsstatus auf der Token-Seite überprüfen. Über Gruppe erstellen Nach Guthaben @@ -853,14 +859,14 @@ Angezeigte Salden Rückgängig machen Dieser Vorgang ist derzeit nicht verfügbar. Bitte versuche es später noch einmal. - Der Kauf von %s ist im Moment nicht verfügbar. Bitte prüfe später ob es Updates gibt. + Der Kauf von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen. Du hast kein Guthaben um dies zu Verkaufen. Lade dein Konto auf, um Geld von dort zu verkaufen. Du hast kein Guthaben zum Versenden. Lade dein Konto auf, um Geld von dort aus senden zu können. - Swappen %s ist im Moment nicht verfügbar. Bitte prüfe später ob es Updates gibt. + Das Tauschen von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen. Das Geld für den Verkauf steht zur Verfügung, sobald die ausstehende(n) Transaktion(en) im Netzwerk %s abgeschlossen ist/sind. Die Geldsendung wird verfügbar, sobald die ausstehende(n) Transaktion(en) im Netzwerk %s abgeschlossen ist/sind. - Der Verkauf von %s ist im Moment nicht verfügbar. Bitte prüfe später ob es Updates gibt. - Staking %s ist aktuell nicht verfügbar. Prüfe bitte ob es ein neues Update gibt. + Der Verkauf von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen. + Staking %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen. Generiere XPUB Ausblenden Du bist dabei, dieses Token vom Hauptbildschirm auszublenden. Du kannst es jederzeit über die Seite „Token verwalten“ wieder hinzufügen. @@ -902,6 +908,7 @@ Diese Aktion ist unumkehrbar. Du hast keinen Zugriff mehr auf die alte Wallet. Tippe auf die Doppelkarte oder Ring mit der Nummer %s und entferne sie erst am Ende des Vorgangs. Verwende %s oder scanne eine Karte oder Ring, um Zugriff auf deine Wallet zu erhalten. + Verbindung fehlgeschlagen: Diese dApp verwendet Wallet Connect Version 1.0, die nicht unterstützt wird. Bitte stelle sicher, dass die dApp Wallet Connect Version 2.0 unterstützt, um eine erfolgreiche Verbindung herzustellen. Bleib auf dem Laufenden mit den neuesten Funktionen und Neuigkeiten Sei der Erste, der von neuen Aktionen erfährt Möchtest du Push-Benachrichtigungen verwenden? @@ -953,6 +960,9 @@ %s Netzwerk Die Adresse wurde erfolgreich kopiert Keine Internetverbindung + Jetzt mit 10 %% Rabatt kaufen + Greife auf 13.000+ Kryptowährungen zu. Kaufe, verkaufe, tausche und stake Sie mit einem einzigen Fingertipp.\nVerbinde bis zu drei Karten für ein Backup. + Entdecke die Tangem Wallet Wallet-Einstellungen Tangem Verwende %s oder scanne eine Karte oder Ring, um den Zugriff auf deine Wallet freizuschalten. @@ -998,6 +1008,9 @@ Dieses Token muss mit deinem Hedera-Konto verknüpft sein, bevor du ihn erhalten kannst. Verknüpfe deinen Token Nicht genug %s. Lade dein Hedera-Konto auf, um dieses Token zuzuordnen + Bist Du sicher, dass Du die Transaktion stornieren willst? Du kannst die Transaktion nicht erneut starten. + Deine Transaktion mit einem Betrag von %1$s %2$s wurde nicht abgeschlossen. Du kannst versuchen, diese erneut abzuschließen. + Du hast eine nicht abgeschlossene Transaktion Auf dieser Karte oder Ring sind nur noch %s Unterschriften übrig. Du musst dein gesamtes Guthaben abheben. Geringe Anzahl von Unterschriften Token in verschiedenen Netzwerken können unterschiedliche Adressen haben. Überprüfe bei der Überweisung noch einmal, ob deine Adresse mit der des Netzwerks übereinstimmt. diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index b77f4a6456..c65acf41fa 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -294,6 +294,9 @@ No disponible para este par Permiso requerido Recomendado + Comprado %s + Comprando %s + Comprando %s... No se encontraron fichas. Por favor intenta con otra solicitud ID : %s ID de transacción copiado @@ -907,6 +910,7 @@ Esta acción es irreversible. No tendrá acceso la billetera antigua. Toque la tarjeta gemela con el número %s y no la retira hasta el final de la operación Use %s o escanee una tarjeta/anillo para tener acceso a su billetera + Error de conexión: Esta dApp utiliza la versión 1.0 de Wallet Connect, que no es compatible. Asegúrese de que la dApp sea compatible con la versión 2.0 de Wallet Connect para conectarse correctamente. Manténgase actualizado con las últimas funciones y noticias Sea el primero en enterarte de nuevas promociones ¿Quiere utilizar\nnotificaciones push? @@ -1006,6 +1010,9 @@ Este token debe estar asociado con su cuenta de Hedera antes de poder recibirlo. Asocie su token No hay suficiente %s. Recargue su cuenta de Hedera para asociar este token + ¿Está seguro(a) de que desea cancelar la transacción? No podrá volver a intentar realizarla. + Su transacción con un importe de %1$s %2$s no se ha completado. Puede volver a intentarlo para completarla. + Tiene una transacción sin terminar Solo quedan %s firmas en esta tarjeta. Deba retirar todos sus fondos. Recuento de firmas bajo Los tokens en diferentes redes pueden tener direcciones diferentes. Verifique que su dirección coincida con la red cuando transfieras fondos. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 346d3081e5..1d37bbc265 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -180,6 +180,7 @@ Conditions d\'utilisation Aujourd\'hui La transaction a échoué + Statut de la transaction Transactions Transfert Je comprends @@ -187,6 +188,7 @@ Inaccessible Unstakez semaine + avec Oui Adresse du contrat copiée ! Réseaux disponibles @@ -292,6 +294,9 @@ Indisponible pour cette paire Permission requise Recommandé + Acheté %s + Achat en cours %s + Achat en cours %s... Aucun jeton trouvé. Veuillez essayer une autre demande ID : %s ID de transaction copié @@ -427,6 +432,7 @@ La valeur théorique totale d\'une crypto-monnaie si toutes les pièces qui pourraient exister étaient en circulation, y compris celles qui ne circulent pas actuellement Valorisation entièrement diluée Date de la Genesis + Vide Haut Détenteurs L\'évolution du nombre de détenteurs de jetons au cours d\'une période donnée @@ -437,6 +443,7 @@ Le changement dans la quantité de liquidité disponible pour le jeton pendant la période spécifiée Liquidité Indice de liquidité + Vide Listé sur Faible Cap. boursière @@ -453,6 +460,7 @@ Performance des prix Dépôt Score de sécurité + Le score de sécurité d\'un jeton est une mesure qui évalue le niveau de sécurité d\'une blockchain ou d\'un jeton en fonction de divers facteurs. Ceci est compilé à partir des sources répertoriées ci-dessous. Social Approvisionnement total Le nombre maximal de pièces ou de jetons pouvant exister pour une crypto-monnaie particulière @@ -509,12 +517,12 @@ Sauvegarde en cours En savoir plus sur les seed phrases - + Vide Écrivez ces %d mots dans l\'ordre indiqué ci-dessous et conservez-les dans un endroit sûr et secret. Votre seed phrase - + Vide %d mots Pour importer votre portefeuille, entrez votre seed phrase dans le champ ci-dessous @@ -566,12 +574,15 @@ Le montant à acheter doit être au moins %s Aucun fournisseur disponible pour cette devise Payer avec + Vous pourrez finaliser votre transaction sur le fournisseur tiers, %s + Redirection vers %s... Nos services ne sont pas disponibles dans ce pays Modifiez-le ou confirmez-le Votre résidence a été identifiée comme Résidence Veuillez sélectionner le bon pays pour garantir des options de paiement et des services précis. Paramètres + Vous pouvez vérifier l\'état de la transaction à partir de la page du jeton Via Grouper Par solde @@ -899,6 +910,7 @@ Cette action est irréversible. Vous n\'aurez plus accès à l\'ancien portefeuille. Appuyez sur la carte jumelle avec le numéro %s et ne la retirez pas jusqu\'à la fin de l\'opération Utilisez %s ou scannez une carte/bague pour avoir accès à votre portefeuille + Échec de la connexion : Cette dApp utilise Wallet Connect version1.0, qui n\'est pas prise en charge. Veuillez vous assurer que la dApp prend en charge Wallet Connect version2.0 pour réussir la connexion. Restez à jour avec les dernières fonctionnalités et actualités Soyez le premier informé des nouvelles promotions Souhaitez-vous utiliser les\nnotifications push? @@ -950,6 +962,9 @@ %s réseau L\'adresse a été copiée avec succès Pas de connexion internet + Obtenez-le maintenant avec 10 %% de réduction + Accédez à plus de 13 000 cryptomonnaies. Achetez, vendez, échangez et stakez en un seul clic.\nAssociez jusqu\'à trois cartes pour une sauvegarde. + Découvrez le Portefeuille Tangem Paramètres du portefeuille Tangem Utilisez %s ou scannez une carte/bague pour déverrouiller l\'accès à votre portefeuille @@ -995,6 +1010,9 @@ Ce jeton doit être associé à votre compte Hedera avant que vous puissiez le recevoir Associez votre jeton %s insuffisant. Renflouez votre compte Hedera pour associer ce jeton + Êtes-vous sûr(e) de vouloir annuler la transaction ? Vous ne pourrez plus réessayer. + Votre transaction d\'un montant de %1$s %2$s n\'a pas été finalisée. Vous pouvez réessayer plus tard pour la finaliser. + Vous avez une transaction inachevée Il ne reste que %s signatures sur cette carte. Vous devez retirer tous vos fonds. Faible nombre de signatures Les jetons sur différents réseaux peuvent avoir des adresses différentes. Vérifiez bien que votre adresse correspond au réseau lorsque vous transférez des fonds. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index f4e7cfccfe..9f3c536b40 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -291,6 +291,9 @@ このペアは利用できません 許可が必要です 推奨 + %sを買い付けました + %sを買い付けています + %sを買い付けています... トークンが見つかりません。別のリクエストをお試しください。 ID: %s 取引IDをコピーしました @@ -895,6 +898,7 @@ この操作は元に戻せません。古いウォレットにはアクセスできなくなります。 番号%sのツインカードをタップし、操作が終了するまで取り外さないでください。 %sを使用するか、カード / リングをスキャンしてウォレットにアクセスしてください + 接続に失敗しました:このdAppは、サポートされていないWallet Connectバージョン1.0を使用しています。正常に接続するには、dAppがWallet Connectバージョン2.0をサポートしていることを確認してください。 最新の機能とニュースをお届けします 新しいプロモーション情報をいち早く入手しましょう プッシュ通知を使用しますか? @@ -994,6 +998,9 @@ このトークンを受け取るには、Hederaアカウントに関連付ける必要があります。 トークンを関連付ける %sが不足しています。このトークンを関連付けるには、Hederaアカウントに資金を追加してください。 + 取引をキャンセルしてもよろしいですか? 取引を再試行することはできません + %1$s %2$sの取引が完了しませんでした。もう一度試して完了してください。 + 未完了の取引があります このカードには%sの署名のみが残っています。資金をすべて引き出す必要があります。 署名数が少ないです 異なるネットワーク上のトークンは、異なるアドレスを持つ場合があります。資金を送金する際には、アドレスがネットワークと一致していることを再確認してください。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 76ba8cfddf..94815eb793 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -913,6 +913,7 @@ Это действие необратимо. У вас не будет доступа к старому кошельку. Приложите twin-карту с номером %s и не убирайте до окончания операции Используйте %s или отсканируйте карту/кольцо, чтобы получить доступ к своему кошельку + Соединение не удалось: это dApp использует Wallet Connect версии 1.0, которая не поддерживается. Убедитесь, что dApp поддерживает Wallet Connect версии 2.0 для успешного подключения. Будьте в курсе новых функций и новостей Узнавайте первым о новых акциях Хотите использовать Push-уведомления? @@ -1012,6 +1013,9 @@ Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять Ассоциируете свой токен Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена + Вы уверены что хотите отменить эту транзакцию? Вы не сможете отправить ее еще раз + Ваша транзакция %1$s %2$s не была завершена. Вы можете попробовать отправить ее еще раз. + У вас есть незавершенная транзакция На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. Малое количество подписей Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети. 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 8f07d981bf..b425f91e95 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -300,6 +300,9 @@ Недоступно для цієї пари Потрібен дозвіл Рекомендовано + Купив %s + Купівля %s... + Купівля %s... Токенів не знайдено. Будь ласка, спробуйте інший запит ID: %s ID транзакції скопійовано @@ -924,6 +927,7 @@ Ця дія є незворотною. Ви не матимете доступу до старого гаманця. Прикладіть twin-картку з номером %s та не прибирайте її до завершення операції Використовуйте %s або відскануйте картку/кільце, щоб отримати доступ до свого гаманця + Не вдалося встановити з\'єднання: Цей dApp використовує Wallet Connect версії 1.0, яка не підтримується. Будь ласка, переконайтеся, що dApp підтримує Wallet Connect версії 2.0 для успішного підключення. Будьте в курсі останніх функцій та новин Дізнавайтеся першими про нові акції Бажаєте використовувати Push-повідомлення? @@ -975,6 +979,9 @@ Мережа %s Адреса скопійована в буфер обміну Немає підключення до інтернету + Отримати з 10%% знижкою + Доступ до 13,000+ криптовалют. Купуйте, продавайте, обмінюйте та стейкайте одним дотиком.\nЗ’єднайте до трьох карток для бекапу. + Відкрийте Tangem Wallet Налаштування гаманця Tangem Використовуйте %s або відскануйте картку/кільце, щоб розблокувати доступ до вашого гаманця @@ -1020,6 +1027,9 @@ Цей токен повинен бути асоційований з вашим обліковим записом Hedera, перш ніж ви зможете його прийняти Асоціюйте свій токен Недостатньо %s. Поповніть ваш обліковий запис Hedera для асоціації цього токена + Ви впевнені, що хочете скасувати цю транзакцію? Ви не зможете відправити її ще раз + Ваша транзакція %1$s %2$s не була завершена. Ви можете спробувати відправити її ще раз. + У вас є незавершена транзакція На цій картці залишається лише %s підписів. Ви повинні вивести всі свої кошти. Низька кількість підписів Токени в різних мережах можуть мати різні адреси. Переконайтеся, що ваша адреса відповідає мережі, коли переказуєте кошти. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 6241677828..60cf7b6306 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -908,6 +908,7 @@ This action is irreversible. You will not have access to the old wallet. Tap the twin card with number %s and do not remove until the end of the operation Use %s or scan a card/ring to have access to your wallet + Connection failed: This dApp uses Wallet Connect version 1.0, which is not supported. Please ensure the dApp supports Wallet Connect version 2.0 to connect successfully. Stay up to date with the latest features and news Be the first to know about new promotions Would you like to use\nPush-notifications? @@ -1007,6 +1008,9 @@ This token must be associated with your Hedera account before you can receive it Associate your token Not enough %s. Top up your Hedera account to associate this token + Are you sure you want to cancel the transaction? You will not be able to retry the transaction again + Your transaction with an amount of %1$s %2$s was not completed. You can try again to complete it. + You have unfinished transaction Only %s signatures are left on this card. You must withdraw all of your funds. Low signature count Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds. diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt index 3db7478662..9f9e73f5ff 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt @@ -3,7 +3,7 @@ package com.tangem.plugin.configuration.model internal object AppConfig { const val packageName = "com.tangem.wallet" const val versionCode = 1 - const val versionName = "1.0.0-SNAPSHOT" + const val versionName = "5.19.0-SNAPSHOT" // const val versionName = "100.0.0-SNAPSHOT" //TODO: [REDACTED_JIRA] const val minSdkVersion = 24 const val targetSdkVersion = 34 From fcbdca43f41d19670bcf2e7f0834236031d291f2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Nov 2024 14:19:21 +0300 Subject: [PATCH 56/99] Updated on 2026-08-14 --- .../feature/swap/viewmodels/SwapViewModel.kt | 18 ++++++------------ .../plugin/configuration/model/AppConfig.kt | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) 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 3aa4b23a82..8250eaf93b 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 @@ -102,7 +102,6 @@ internal class SwapViewModel @Inject constructor( get() = if (walletFeatureToggles.isMainActionButtonsEnabled) { savedStateHandle.get(AppRoute.Swap.CURRENCY_TO_KEY) ?.unbundle(CryptoCurrency.serializer()) - ?: error("no expected parameter CryptoCurrency (to) found") } else { null } @@ -172,8 +171,7 @@ internal class SwapViewModel @Inject constructor( val fromStatus = getCryptoCurrencyStatusUseCase(userWalletId, initialCurrencyFrom.id).getOrNull() val toStatus = initialCurrencyTo?.let { getCryptoCurrencyStatusUseCase(userWalletId, it.id).getOrNull() } val wallet = getUserWalletUseCase(userWalletId).getOrNull() - val isStatusToNull = walletFeatureToggles.isMainActionButtonsEnabled && toStatus == null - if (fromStatus == null || wallet == null || isStatusToNull) { + if (fromStatus == null || wallet == null) { uiState = stateBuilder.addAlert(uiState = uiState, onDismiss = swapRouter::back) } else { userWallet = wallet @@ -228,15 +226,11 @@ internal class SwapViewModel @Inject constructor( swapInteractor.getTokensDataState(initialCurrencyFrom) }.onSuccess { state -> updateTokensState(state) - val selectedCurrency = if (walletFeatureToggles.isMainActionButtonsEnabled) { - initialToStatus - } else { - swapInteractor.getInitialCurrencyToSwap( - initialCryptoCurrency = initialCurrencyFrom, - state = state, - isReverseFromTo = isReverseFromTo, - ) - } + val selectedCurrency = initialToStatus ?: swapInteractor.getInitialCurrencyToSwap( + initialCryptoCurrency = initialCurrencyFrom, + state = state, + isReverseFromTo = isReverseFromTo, + ) applyInitialTokenChoice( state = state, diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt index 9f9e73f5ff..3db7478662 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt @@ -3,7 +3,7 @@ package com.tangem.plugin.configuration.model internal object AppConfig { const val packageName = "com.tangem.wallet" const val versionCode = 1 - const val versionName = "5.19.0-SNAPSHOT" + const val versionName = "1.0.0-SNAPSHOT" // const val versionName = "100.0.0-SNAPSHOT" //TODO: [REDACTED_JIRA] const val minSdkVersion = 24 const val targetSdkVersion = 34 From f53ffa9a645663251234e9fea0cf3e45e68b41b4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 21 Nov 2024 14:50:12 +0500 Subject: [PATCH 57/99] Updated on 2026-08-14 --- .../tap/di/domain/OnrampDomainModule.kt | 6 ++ .../tangem/datasource/di/OnrampStoreModule.kt | 20 +++- .../onramp/pairs/DefaultOnrampPairsStore.kt | 11 ++ .../local/onramp/pairs/OnrampPairsStore.kt | 9 ++ .../DefaultOnrampPaymentMethodsStore.kt | 2 +- .../OnrampPaymentMethodsStore.kt | 4 +- .../onramp/quotes/DefaultOnrampQuotesStore.kt | 11 ++ .../local/onramp/quotes/OnrampQuotesStore.kt | 9 ++ .../data/onramp/DefaultOnrampRepository.kt | 101 +++++++++++++++++- .../tangem/data/onramp/di/OnrampDataModule.kt | 8 +- .../onramp/CheckOnrampAvailabilityUseCase.kt | 8 +- .../domain/onramp/ClearOnrampCacheUseCase.kt | 10 ++ .../onramp/repositories/OnrampRepository.kt | 11 +- .../main/model/OnrampMainComponentModel.kt | 9 +- 14 files changed, 206 insertions(+), 13 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/onramp/pairs/DefaultOnrampPairsStore.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/onramp/pairs/OnrampPairsStore.kt rename core/datasource/src/main/java/com/tangem/datasource/local/onramp/{ => paymentmethods}/DefaultOnrampPaymentMethodsStore.kt (89%) rename core/datasource/src/main/java/com/tangem/datasource/local/onramp/{ => paymentmethods}/OnrampPaymentMethodsStore.kt (78%) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/onramp/quotes/DefaultOnrampQuotesStore.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/onramp/quotes/OnrampQuotesStore.kt create mode 100644 domain/onramp/src/main/java/com/tangem/domain/onramp/ClearOnrampCacheUseCase.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 d21153b84b..4b3fb3f472 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 @@ -102,4 +102,10 @@ internal object OnrampDomainModule { fun provideGetOnrampPaymentMethodsUseCase(onrampRepository: OnrampRepository): GetOnrampPaymentMethodsUseCase { return GetOnrampPaymentMethodsUseCase(onrampRepository) } + + @Provides + @Singleton + fun provideClearOnrampCacheUseCase(onrampRepository: OnrampRepository): ClearOnrampCacheUseCase { + return ClearOnrampCacheUseCase(onrampRepository) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt index 8b7fb87fa2..def89f2cb1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt @@ -1,8 +1,12 @@ package com.tangem.datasource.di import com.tangem.datasource.local.datastore.RuntimeDataStore -import com.tangem.datasource.local.onramp.DefaultOnrampPaymentMethodsStore -import com.tangem.datasource.local.onramp.OnrampPaymentMethodsStore +import com.tangem.datasource.local.onramp.pairs.DefaultOnrampPairsStore +import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore +import com.tangem.datasource.local.onramp.paymentmethods.DefaultOnrampPaymentMethodsStore +import com.tangem.datasource.local.onramp.paymentmethods.OnrampPaymentMethodsStore +import com.tangem.datasource.local.onramp.quotes.DefaultOnrampQuotesStore +import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -18,4 +22,16 @@ internal object OnrampStoreModule { fun provideOnrampPaymentMethodsStore(): OnrampPaymentMethodsStore { return DefaultOnrampPaymentMethodsStore(dataStore = RuntimeDataStore()) } + + @Provides + @Singleton + fun provideOnrampPairsStore(): OnrampPairsStore { + return DefaultOnrampPairsStore(dataStore = RuntimeDataStore()) + } + + @Provides + @Singleton + fun provideOnrampQuotesStore(): OnrampQuotesStore { + return DefaultOnrampQuotesStore(dataStore = RuntimeDataStore()) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/pairs/DefaultOnrampPairsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/pairs/DefaultOnrampPairsStore.kt new file mode 100644 index 0000000000..636106284b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/pairs/DefaultOnrampPairsStore.kt @@ -0,0 +1,11 @@ +package com.tangem.datasource.local.onramp.pairs + +import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO +import com.tangem.datasource.local.datastore.core.StringKeyDataStore +import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator + +internal class DefaultOnrampPairsStore( + dataStore: StringKeyDataStore>, +) : OnrampPairsStore, StringKeyDataStoreDecorator>(dataStore) { + override fun provideStringKey(key: String): String = key +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/pairs/OnrampPairsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/pairs/OnrampPairsStore.kt new file mode 100644 index 0000000000..1b32894636 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/pairs/OnrampPairsStore.kt @@ -0,0 +1,9 @@ +package com.tangem.datasource.local.onramp.pairs + +import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO + +interface OnrampPairsStore { + suspend fun getSyncOrNull(key: String): List? + suspend fun store(key: String, value: List) + suspend fun clear() +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/DefaultOnrampPaymentMethodsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/paymentmethods/DefaultOnrampPaymentMethodsStore.kt similarity index 89% rename from core/datasource/src/main/java/com/tangem/datasource/local/onramp/DefaultOnrampPaymentMethodsStore.kt rename to core/datasource/src/main/java/com/tangem/datasource/local/onramp/paymentmethods/DefaultOnrampPaymentMethodsStore.kt index 3a4fa1b949..1dacddfbc0 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/DefaultOnrampPaymentMethodsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/paymentmethods/DefaultOnrampPaymentMethodsStore.kt @@ -1,4 +1,4 @@ -package com.tangem.datasource.local.onramp +package com.tangem.datasource.local.onramp.paymentmethods import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO import com.tangem.datasource.local.datastore.core.StringKeyDataStore diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/OnrampPaymentMethodsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/paymentmethods/OnrampPaymentMethodsStore.kt similarity index 78% rename from core/datasource/src/main/java/com/tangem/datasource/local/onramp/OnrampPaymentMethodsStore.kt rename to core/datasource/src/main/java/com/tangem/datasource/local/onramp/paymentmethods/OnrampPaymentMethodsStore.kt index a6847ae671..fc1eca40d4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/OnrampPaymentMethodsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/paymentmethods/OnrampPaymentMethodsStore.kt @@ -1,4 +1,4 @@ -package com.tangem.datasource.local.onramp +package com.tangem.datasource.local.onramp.paymentmethods import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO @@ -9,4 +9,6 @@ interface OnrampPaymentMethodsStore { suspend fun store(key: String, value: List) suspend fun contains(key: String): Boolean + + suspend fun clear() } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/quotes/DefaultOnrampQuotesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/quotes/DefaultOnrampQuotesStore.kt new file mode 100644 index 0000000000..fb7a157350 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/quotes/DefaultOnrampQuotesStore.kt @@ -0,0 +1,11 @@ +package com.tangem.datasource.local.onramp.quotes + +import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse +import com.tangem.datasource.local.datastore.core.StringKeyDataStore +import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator + +internal class DefaultOnrampQuotesStore( + dataStore: StringKeyDataStore>, +) : OnrampQuotesStore, StringKeyDataStoreDecorator>(dataStore) { + override fun provideStringKey(key: String): String = key +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/quotes/OnrampQuotesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/quotes/OnrampQuotesStore.kt new file mode 100644 index 0000000000..0e7034f3b2 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/quotes/OnrampQuotesStore.kt @@ -0,0 +1,9 @@ +package com.tangem.datasource.local.onramp.quotes + +import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse + +interface OnrampQuotesStore { + suspend fun getSyncOrNull(key: String): List? + suspend fun store(key: String, value: List) + suspend fun clear() +} \ No newline at end of file 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 4145803c20..8225a6295c 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 @@ -3,13 +3,18 @@ package com.tangem.data.onramp import com.tangem.data.common.api.safeApiCall import com.tangem.data.onramp.converters.CountryConverter import com.tangem.data.onramp.converters.CurrencyConverter -import com.tangem.data.onramp.converters.StatusConverter import com.tangem.data.onramp.converters.PaymentMethodConverter +import com.tangem.data.onramp.converters.StatusConverter import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.express.models.TangemExpressValues import com.tangem.datasource.api.onramp.OnrampApi +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.model.OnrampCountryDTO import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO -import com.tangem.datasource.local.onramp.OnrampPaymentMethodsStore +import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore +import com.tangem.datasource.local.onramp.paymentmethods.OnrampPaymentMethodsStore +import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObject @@ -17,10 +22,13 @@ 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.model.OnrampStatus import com.tangem.domain.onramp.model.OnrampPaymentMethod +import com.tangem.domain.onramp.model.OnrampStatus import com.tangem.domain.onramp.repositories.OnrampRepository +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext @@ -31,6 +39,8 @@ internal class DefaultOnrampRepository( private val dispatchers: CoroutineDispatcherProvider, private val appPreferencesStore: AppPreferencesStore, private val paymentMethodsStore: OnrampPaymentMethodsStore, + private val pairsStore: OnrampPairsStore, + private val quotesStore: OnrampQuotesStore, ) : OnrampRepository { private val currencyConverter = CurrencyConverter() @@ -100,8 +110,8 @@ internal class DefaultOnrampRepository( .map { it?.let(countryConverter::convert) } } - override suspend fun fetchPaymentMethodsIfAbsent() { - if (paymentMethodsStore.contains(PAYMENT_METHODS_KEY)) return + override suspend fun fetchPaymentMethodsIfAbsent() = withContext(dispatchers.io) { + if (paymentMethodsStore.contains(PAYMENT_METHODS_KEY)) return@withContext val response = safeApiCall( call = { onrampApi.getPaymentMethods().bind() }, @@ -113,6 +123,74 @@ internal class DefaultOnrampRepository( paymentMethodsStore.store(PAYMENT_METHODS_KEY, response) } + override suspend fun fetchPairs(currency: OnrampCurrency, country: OnrampCountry, cryptoCurrency: CryptoCurrency) = + withContext(dispatchers.io) { + val pairs = safeApiCall( + call = { + onrampApi.getPairs( + body = OnrampPairsRequest( + fromCurrencyCode = currency.code, + countryCode = country.code, + to = listOf( + OnrampDestinationDTO( + contractAddress = cryptoCurrency.getContractAddress(), + network = cryptoCurrency.network.backendId, + ), + ), + ), + ).bind() + }, + onError = { + Timber.w(it, "Unable to fetch onramp pairs") + throw it + }, + ) + pairsStore.store(PAIRS_KEY, pairs) + } + + override suspend fun fetchQuotes( + currency: OnrampCurrency, + country: OnrampCountry, + cryptoCurrency: CryptoCurrency, + fromAmount: String, + ) = withContext(dispatchers.io) { + val pairs = requireNotNull(pairsStore.getSyncOrNull(PAIRS_KEY)) { + "Unable to get pairs. At this point they must not be null." + } + val quotes = pairs.flatMap { pair -> + pair.providers.flatMap { provider -> + provider.paymentMethods.map { paymentMethod -> + async { + safeApiCall( + call = { + onrampApi.getQuote( + fromCurrencyCode = currency.code, + toContractAddress = cryptoCurrency.getContractAddress(), + toNetwork = cryptoCurrency.network.backendId, + paymentMethod = paymentMethod, + countryCode = country.code, + fromAmount = fromAmount, + toDecimals = cryptoCurrency.decimals, + providerId = provider.providerId, + ).bind() + }, + onError = { + Timber.w( + it, + "Unable to fetch onramp quotes for ${provider.providerId} -> $paymentMethod", + ) + null + }, + ) + } + } + } + } + .awaitAll() + .filterNotNull() + quotesStore.store(QUOTES_KEY, quotes) + } + override suspend fun getPaymentMethods(): List { val paymentMethods = requireNotNull(paymentMethodsStore.getSyncOrNull(PAYMENT_METHODS_KEY)) { "Onramp payment methods is absent in storage" @@ -120,7 +198,20 @@ internal class DefaultOnrampRepository( return paymentMethodsConverter.convertList(paymentMethods) } + override suspend fun clearCache() { + paymentMethodsStore.clear() + pairsStore.clear() + quotesStore.clear() + } + + private fun CryptoCurrency.getContractAddress(): String = when (this) { + is CryptoCurrency.Coin -> TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE + is CryptoCurrency.Token -> this.contractAddress + } + private companion object { const val PAYMENT_METHODS_KEY = "onramp_payment_methods" + const val PAIRS_KEY = "onramp_pairs" + const val QUOTES_KEY = "onramp_quotes" } } \ 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 3731a0773d..1d19134538 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 @@ -4,7 +4,9 @@ import com.tangem.data.onramp.DefaultOnrampErrorResolver import com.tangem.data.onramp.DefaultOnrampRepository import com.tangem.data.onramp.DefaultOnrampTransactionRepository import com.tangem.datasource.api.onramp.OnrampApi -import com.tangem.datasource.local.onramp.OnrampPaymentMethodsStore +import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore +import com.tangem.datasource.local.onramp.paymentmethods.OnrampPaymentMethodsStore +import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampRepository @@ -27,12 +29,16 @@ internal object OnrampDataModule { dispatchers: CoroutineDispatcherProvider, appPreferencesStore: AppPreferencesStore, paymentMethodsStore: OnrampPaymentMethodsStore, + pairsStore: OnrampPairsStore, + quotesStore: OnrampQuotesStore, ): OnrampRepository { return DefaultOnrampRepository( onrampApi = onrampApi, dispatchers = dispatchers, appPreferencesStore = appPreferencesStore, paymentMethodsStore = paymentMethodsStore, + pairsStore = pairsStore, + quotesStore = quotesStore, ) } 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 index 2e099164d9..60a38a8c5f 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt @@ -3,10 +3,11 @@ package com.tangem.domain.onramp import arrow.core.Either import com.tangem.domain.onramp.model.OnrampAvailability import com.tangem.domain.onramp.repositories.OnrampRepository +import com.tangem.domain.tokens.model.CryptoCurrency class CheckOnrampAvailabilityUseCase(private val repository: OnrampRepository) { - suspend operator fun invoke(): Either { + suspend operator fun invoke(cryptoCurrency: CryptoCurrency): Either { return Either.catch { repository.fetchPaymentMethodsIfAbsent() repository.getDefaultCountrySync()?.let { savedCountry -> @@ -15,6 +16,11 @@ class CheckOnrampAvailabilityUseCase(private val repository: OnrampRepository) { repository.saveDefaultCurrency(savedCountry.defaultCurrency) savedCountry.defaultCurrency } + repository.fetchPairs( + currency = currency, + country = savedCountry, + cryptoCurrency = cryptoCurrency, + ) OnrampAvailability.Available(country = savedCountry, currency = currency) } else { OnrampAvailability.NotSupported(savedCountry) diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/ClearOnrampCacheUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/ClearOnrampCacheUseCase.kt new file mode 100644 index 0000000000..42fd4f7930 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/ClearOnrampCacheUseCase.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.onramp + +import com.tangem.domain.onramp.repositories.OnrampRepository + +class ClearOnrampCacheUseCase(private val repository: OnrampRepository) { + + suspend operator fun invoke() { + repository.clearCache() + } +} \ 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 1c269c1a75..6bd6e7466b 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,9 @@ package com.tangem.domain.onramp.repositories import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.domain.onramp.model.OnrampCurrency -import com.tangem.domain.onramp.model.OnrampStatus import com.tangem.domain.onramp.model.OnrampPaymentMethod +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.tokens.model.CryptoCurrency import kotlinx.coroutines.flow.Flow interface OnrampRepository { @@ -13,6 +14,13 @@ interface OnrampRepository { suspend fun getCountryByIp(): OnrampCountry suspend fun getStatus(txId: String): OnrampStatus suspend fun fetchPaymentMethodsIfAbsent() + suspend fun fetchPairs(currency: OnrampCurrency, country: OnrampCountry, cryptoCurrency: CryptoCurrency) + suspend fun fetchQuotes( + currency: OnrampCurrency, + country: OnrampCountry, + cryptoCurrency: CryptoCurrency, + fromAmount: String, + ) // cache suspend fun saveDefaultCurrency(currency: OnrampCurrency) @@ -22,4 +30,5 @@ interface OnrampRepository { suspend fun getDefaultCountrySync(): OnrampCountry? fun getDefaultCountry(): Flow suspend fun getPaymentMethods(): List + suspend fun clearCache() } \ 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 index 50834be67d..ad2d439de3 100644 --- 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 @@ -6,6 +6,7 @@ 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.domain.onramp.ClearOnrampCacheUseCase import com.tangem.domain.onramp.GetOnrampCurrencyUseCase import com.tangem.domain.onramp.model.OnrampAvailability import com.tangem.domain.onramp.model.OnrampCurrency @@ -27,6 +28,7 @@ internal class OnrampMainComponentModel @Inject constructor( private val router: Router, private val checkOnrampAvailabilityUseCase: CheckOnrampAvailabilityUseCase, private val getOnrampCurrencyUseCase: GetOnrampCurrencyUseCase, + private val clearOnrampCacheUseCase: ClearOnrampCacheUseCase, paramsContainer: ParamsContainer, ) : Model(), OnrampIntents { @@ -53,7 +55,7 @@ internal class OnrampMainComponentModel @Inject constructor( private fun checkResidenceCountry() { modelScope.launch { - checkOnrampAvailabilityUseCase.invoke() + checkOnrampAvailabilityUseCase.invoke(params.cryptoCurrency) .onRight(::handleOnrampAvailability) .onLeft { Timber.e(it) } } @@ -99,4 +101,9 @@ internal class OnrampMainComponentModel @Inject constructor( override fun openCurrenciesList() { bottomSheetNavigation.activate(OnrampMainBottomSheetConfig.CurrenciesList) } + + override fun onDestroy() { + modelScope.launch { clearOnrampCacheUseCase.invoke() } + super.onDestroy() + } } \ No newline at end of file From 5a6dfd50e4f44713c7b5b72cff1453017bd9b602 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Nov 2024 15:16:33 +0500 Subject: [PATCH 58/99] Updated on 2026-08-14 --- .../com/tangem/core/ui/utils/ImageBackgroundContrastChecker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/ImageBackgroundContrastChecker.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/ImageBackgroundContrastChecker.kt index 7c9071598f..ab859df2bb 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/ImageBackgroundContrastChecker.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/ImageBackgroundContrastChecker.kt @@ -39,6 +39,6 @@ class ImageBackgroundContrastChecker( private companion object { // https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef - private const val LOW_CONTRAST_RATIO = 1.1f + private const val LOW_CONTRAST_RATIO = 1f } } \ No newline at end of file From c6497a0abcc082c26e455a183dc61b0f928dc046 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 21 Nov 2024 17:49:53 +0300 Subject: [PATCH 59/99] Updated on 2026-08-14 --- .idea/inspectionProfiles/Project_Default.xml | 32 ++++ features/onboarding-v2/impl/build.gradle.kts | 5 +- .../MultiWalletCreateWalletComponent.kt | 2 +- .../model/MultiWalletCreateWalletModel.kt | 3 +- .../ui/MultiWalletCreateWallet.kt | 6 +- .../ui/state/MultiWalletCreateWalletUM.kt | 18 +- .../MultiWalletSeedPhraseComponent.kt | 41 ++++- .../model/MultiWalletSeedPhraseModel.kt | 123 ++++++++++++- .../child/seedphrase/model/SeedPhraseState.kt | 2 + .../GenerateSeedPhraseUiStateBuilder.kt | 2 +- .../builder/ImportSeedPhraseUiStateBuilder.kt | 163 ++++++++++++++++++ .../builder/SeedPhraseCheckUiStateBuilder.kt | 2 +- .../seedphrase/ui/MultiWalletSeedPhrase.kt | 2 +- .../ui/MultiWalletSeedPhraseImport.kt | 45 +++-- .../ui/MultiWalletSeedPhraseWords.kt | 5 +- .../ui/MultiWalletSeedPhraseWordsCheck.kt | 22 ++- .../ui/state/MultiWalletSeedPhraseUM.kt | 5 +- .../impl/common/ui/OnboardingDialogUM.kt | 15 ++ .../ui/ResetCardDialog.kt} | 9 +- 19 files changed, 448 insertions(+), 54 deletions(-) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/ImportSeedPhraseUiStateBuilder.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/OnboardingDialogUM.kt rename features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/{child/createwallet/model/Dialogs.kt => common/ui/ResetCardDialog.kt} (65%) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 6edc1d9d21..5647ad7cfc 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -4,8 +4,36 @@ + + + + + + + + + + + + + + + + @@ -19,6 +47,10 @@ + +

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%0D$8;j`)os) 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