Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-15 15:59:41 +05:00
parent 40b616868d
commit 2a0e8f78a6
22 changed files with 236 additions and 54 deletions

View file

@ -31,6 +31,7 @@ dependencies {
implementation(project(":domain:wallets:models"))
implementation(projects.domain.settings)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)
implementation(projects.domain.txhistory)
implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models)

View file

@ -0,0 +1,43 @@
package com.tangem.tap.features.wallet.converters
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.store
import com.tangem.utils.converter.Converter
class CryptoCurrencyConverter : Converter<Currency, CryptoCurrency> {
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
override fun convert(value: Currency): CryptoCurrency {
return when (value) {
is Currency.Blockchain -> requireNotNull(
cryptoCurrencyFactory.createCoin(
blockchain = value.blockchain,
derivationStyleProvider = requireNotNull(
store.state.globalState
.userWalletsListManager
?.selectedUserWalletSync
?.scanResponse
?.derivationStyleProvider,
),
),
)
is Currency.Token -> requireNotNull(
cryptoCurrencyFactory.createToken(
sdkToken = value.token,
blockchain = value.blockchain,
derivationStyleProvider = requireNotNull(
store.state.globalState
.userWalletsListManager
?.selectedUserWalletSync
?.scanResponse
?.derivationStyleProvider,
),
),
)
}
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.wallet.redux.middlewares
import androidx.core.os.bundleOf
import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.guard
import com.tangem.common.flatMap
@ -7,6 +8,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken
import com.tangem.tap.common.extensions.addContext
@ -15,6 +17,7 @@ import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.features.wallet.converters.CryptoCurrencyConverter
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.models.WalletDialog
@ -28,12 +31,19 @@ import kotlinx.coroutines.launch
import timber.log.Timber
class MultiWalletMiddleware {
private val cryptoCurrencyConverter by lazy { CryptoCurrencyConverter() }
@Suppress("LongMethod", "ComplexMethod")
fun handle(action: WalletAction.MultiWallet, walletState: WalletState?) {
when (action) {
is WalletAction.MultiWallet.SelectWallet -> {
if (action.currency != null) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletDetails))
val bundle = bundleOf(
// TODO: [REDACTED_JIRA]
TokenDetailsRouter.SELECTED_CURRENCY_KEY to cryptoCurrencyConverter.convert(action.currency),
)
store.dispatch(NavigationAction.NavigateTo(screen = AppScreen.WalletDetails, bundle = bundle))
}
}
is WalletAction.MultiWallet.TryToRemoveWallet -> {

View file

@ -9,11 +9,11 @@ import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.models.CryptoCurrency
import timber.log.Timber
import com.tangem.blockchain.common.Token as SdkToken
internal class CardCurrenciesFactory(private val demoConfig: DemoConfig) {
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
fun createDefaultCoinsForMultiCurrencyCard(
card: CardDTO,
derivationStyleProvider: DerivationStyleProvider,
@ -28,7 +28,7 @@ internal class CardCurrenciesFactory(private val demoConfig: DemoConfig) {
blockchains = blockchains.mapNotNull { it.getTestnetVersion() }
}
return blockchains.mapNotNull { createCoin(it, derivationStyleProvider) }
return blockchains.mapNotNull { cryptoCurrencyFactory.createCoin(it, derivationStyleProvider) }
}
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
@ -36,56 +36,13 @@ internal class CardCurrenciesFactory(private val demoConfig: DemoConfig) {
val resolver = scanResponse.cardTypesResolver
val blockchain = resolver.getBlockchain()
val coin = requireNotNull(createCoin(blockchain, derivationStyleProvider)) {
val coin = requireNotNull(cryptoCurrencyFactory.createCoin(blockchain, derivationStyleProvider)) {
"Coin for the single currency card cannot be null"
}
val primaryToken = resolver.getPrimaryToken()?.let { token ->
createToken(token, blockchain, derivationStyleProvider)
cryptoCurrencyFactory.createToken(token, blockchain, derivationStyleProvider)
}
return primaryToken ?: coin
}
private fun createToken(
sdkToken: SdkToken,
blockchain: Blockchain,
derivationStyleProvider: DerivationStyleProvider,
): CryptoCurrency.Token? {
if (blockchain != Blockchain.Unknown) {
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
return null
}
return CryptoCurrency.Token(
id = getTokenId(blockchain, sdkToken),
networkId = getNetworkId(blockchain),
name = sdkToken.name,
symbol = sdkToken.symbol,
iconUrl = getTokenIconUrl(blockchain, sdkToken),
decimals = sdkToken.decimals,
isCustom = false,
contractAddress = sdkToken.contractAddress,
derivationPath = getDerivationPath(blockchain, derivationStyleProvider),
)
}
private fun createCoin(
blockchain: Blockchain,
derivationStyleProvider: DerivationStyleProvider,
): CryptoCurrency.Coin? {
if (blockchain == Blockchain.Unknown) {
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
return null
}
return CryptoCurrency.Coin(
id = getCoinId(blockchain),
networkId = getNetworkId(blockchain),
name = blockchain.fullName,
symbol = blockchain.currency,
iconUrl = getCoinIconUrl(blockchain),
decimals = blockchain.decimals(),
derivationPath = getDerivationPath(blockchain, derivationStyleProvider),
)
}
}

View file

@ -0,0 +1,53 @@
package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token as SdkToken
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.tokens.models.CryptoCurrency
import timber.log.Timber
class CryptoCurrencyFactory {
fun createToken(
sdkToken: SdkToken,
blockchain: Blockchain,
derivationStyleProvider: DerivationStyleProvider,
): CryptoCurrency.Token? {
if (blockchain == Blockchain.Unknown) {
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
return null
}
val id = getTokenId(blockchain, sdkToken)
return CryptoCurrency.Token(
id = id,
networkId = getNetworkId(blockchain),
name = sdkToken.name,
symbol = sdkToken.symbol,
iconUrl = getTokenIconUrl(blockchain, sdkToken),
decimals = sdkToken.decimals,
isCustom = isCustomToken(id),
contractAddress = sdkToken.contractAddress,
derivationPath = getDerivationPath(blockchain, derivationStyleProvider),
blockchainName = blockchain.fullName,
standardType = getTokenStandardType(blockchain, sdkToken),
)
}
fun createCoin(blockchain: Blockchain, derivationStyleProvider: DerivationStyleProvider): CryptoCurrency.Coin? {
if (blockchain == Blockchain.Unknown) {
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
return null
}
return CryptoCurrency.Coin(
id = getCoinId(blockchain),
networkId = getNetworkId(blockchain),
name = blockchain.fullName,
symbol = blockchain.currency,
iconUrl = getCoinIconUrl(blockchain),
decimals = blockchain.decimals(),
derivationPath = getDerivationPath(blockchain, derivationStyleProvider),
)
}
}

View file

@ -84,6 +84,8 @@ internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) {
iconUrl = getTokenIconUrl(blockchain, sdkToken),
contractAddress = sdkToken.contractAddress,
isCustom = isCustomToken(id),
blockchainName = blockchain.fullName,
standardType = getTokenStandardType(blockchain, sdkToken),
)
}
}

View file

@ -6,6 +6,7 @@ import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.extensions.derivationPath
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.tokens.models.CryptoCurrency.ID
import com.tangem.domain.tokens.models.Network
import com.tangem.blockchain.common.Token as SdkToken
@ -45,6 +46,16 @@ internal fun getTokenId(blockchain: Blockchain, token: SdkToken): ID {
return getTokenOrCoinId(blockchain, token)
}
internal fun getTokenStandardType(blockchain: Blockchain, token: SdkToken): CryptoCurrency.StandardType {
return when (blockchain) {
Blockchain.Ethereum, Blockchain.EthereumTestnet -> CryptoCurrency.StandardType.ERC20
Blockchain.BSC, Blockchain.BSCTestnet -> CryptoCurrency.StandardType.BEP20
Blockchain.Binance, Blockchain.BinanceTestnet -> CryptoCurrency.StandardType.BEP2
Blockchain.Tron, Blockchain.TronTestnet -> CryptoCurrency.StandardType.TRC20
else -> CryptoCurrency.StandardType.Unspecified(token.name)
}
}
internal fun getTokenIconUrl(blockchain: Blockchain, token: SdkToken): String? {
val tokenId = token.id

View file

@ -1,5 +1,7 @@
package com.tangem.domain.tokens.models
import java.io.Serializable
/**
* Represents a generic cryptocurrency.
*
@ -12,7 +14,7 @@ package com.tangem.domain.tokens.models
* @property derivationPath Optional path used for key derivation. `null` if the wallet does not support the
* [HD Wallet](https://coinsutra.com/hd-wallets-deterministic-wallet/) feature.
*/
sealed class CryptoCurrency {
sealed class CryptoCurrency : Serializable {
abstract val id: ID
abstract val networkId: Network.ID
@ -56,6 +58,8 @@ sealed class CryptoCurrency {
override val derivationPath: String?,
val contractAddress: String,
val isCustom: Boolean,
val blockchainName: String, // TODO: Move this field to proper entity
val standardType: StandardType, // TODO: Move this field to proper entity
) : CryptoCurrency() {
init {
@ -130,6 +134,26 @@ sealed class CryptoCurrency {
}
}
sealed class StandardType {
abstract val name: String
object ERC20 : StandardType() {
override val name: String = "ERC20"
}
object TRC20 : StandardType() {
override val name: String = "TRC20"
}
object BEP20 : StandardType() {
override val name: String = "BEP20"
}
object BEP2 : StandardType() {
override val name: String = "BEP2"
}
class Unspecified(val tokenName: String) : StandardType() {
override val name: String = tokenName
}
}
protected fun checkProperties() {
require(name.isNotBlank()) { "Crypto currency name must not be blank" }
require(symbol.isNotBlank()) { "Crypto currency symbol must not be blank" }

View file

@ -26,6 +26,8 @@ internal object MockTokens {
iconUrl = null,
contractAddress = "address",
derivationPath = null,
blockchainName = "Ethereum",
standardType = CryptoCurrency.StandardType.ERC20,
)
val token3
get() = CryptoCurrency.Token(
@ -38,6 +40,8 @@ internal object MockTokens {
iconUrl = null,
contractAddress = "address",
derivationPath = null,
blockchainName = "Ethereum",
standardType = CryptoCurrency.StandardType.ERC20,
)
val token4
get() = CryptoCurrency.Coin(
@ -60,6 +64,8 @@ internal object MockTokens {
iconUrl = null,
contractAddress = "address",
derivationPath = null,
blockchainName = "Ethereum",
standardType = CryptoCurrency.StandardType.ERC20,
)
val token6
get() = CryptoCurrency.Token(
@ -72,6 +78,8 @@ internal object MockTokens {
iconUrl = null,
contractAddress = "address",
derivationPath = null,
blockchainName = "Ethereum",
standardType = CryptoCurrency.StandardType.ERC20,
)
val token7
get() = CryptoCurrency.Coin(
@ -94,6 +102,8 @@ internal object MockTokens {
iconUrl = null,
contractAddress = "address",
derivationPath = null,
blockchainName = "Ethereum",
standardType = CryptoCurrency.StandardType.ERC20,
)
val token9
get() = CryptoCurrency.Token(
@ -106,6 +116,8 @@ internal object MockTokens {
iconUrl = null,
contractAddress = "address",
derivationPath = null,
blockchainName = "Ethereum",
standardType = CryptoCurrency.StandardType.ERC20,
)
val token10
get() = CryptoCurrency.Token(
@ -118,6 +130,8 @@ internal object MockTokens {
iconUrl = null,
contractAddress = "address",
derivationPath = null,
blockchainName = "Ethereum",
standardType = CryptoCurrency.StandardType.ERC20,
)
val tokens = listOf(token1, token2, token3, token4, token5, token6, token7, token8, token9, token10)

View file

@ -5,4 +5,8 @@ import androidx.fragment.app.Fragment
interface TokenDetailsRouter {
fun getEntryFragment(): Fragment
companion object {
const val SELECTED_CURRENCY_KEY = "selected_currency"
}
}

View file

@ -38,6 +38,7 @@ dependencies {
implementation(projects.core.ui)
implementation(projects.core.navigation)
implementation(projects.domain.tokens.models)
implementation(projects.domain.txhistory)
implementation(projects.domain.txhistory.models)

View file

@ -3,10 +3,15 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState
import com.tangem.features.tokendetails.impl.R
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -16,9 +21,14 @@ import kotlin.properties.Delegates
private const val LOADING_DELAY = 4_000L
@HiltViewModel
internal class TokenDetailsViewModel @Inject constructor() : ViewModel() {
internal class TokenDetailsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
) : ViewModel() {
var router: InnerTokenDetailsRouter by Delegates.notNull()
private val cryptoCurrency: CryptoCurrency = savedStateHandle[TokenDetailsRouter.SELECTED_CURRENCY_KEY]
?: error("no expected parameter CryptoCurrency found")
var router by Delegates.notNull<InnerTokenDetailsRouter>()
var uiState by mutableStateOf(getInitialState())
private set
@ -38,6 +48,19 @@ internal class TokenDetailsViewModel @Inject constructor() : ViewModel() {
topAppBarConfig = TokenDetailsPreviewData.tokenDetailsTopAppBarConfig.copy(
onBackClick = ::onBackClick,
),
tokenInfoBlockState = TokenInfoBlockState(
name = cryptoCurrency.name,
iconUrl = requireNotNull(cryptoCurrency.iconUrl),
currency = when (cryptoCurrency) {
is CryptoCurrency.Coin -> TokenInfoBlockState.Currency.Native
is CryptoCurrency.Token -> TokenInfoBlockState.Currency.Token(
networkName = cryptoCurrency.standardType.name,
blockchainName = cryptoCurrency.blockchainName,
// TODO: [REDACTED_JIRA]
networkIcon = R.drawable.img_eth_22,
)
},
),
)
private fun onBackClick() {

View file

@ -61,4 +61,5 @@ dependencies {
/** Feature Apis */
implementation(projects.features.wallet.api)
implementation(projects.features.tokendetails.api)
}

View file

@ -100,6 +100,7 @@ internal object WalletPreviewData {
type = PriceChangeConfig.Type.UP,
),
),
onClick = {},
)
}
@ -118,6 +119,7 @@ internal object WalletPreviewData {
type = PriceChangeConfig.Type.UP,
),
),
onClick = {},
)
}

View file

@ -61,6 +61,7 @@ internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) {
private fun ContentTokenItem(content: TokenItemState.Content, modifier: Modifier = Modifier) {
InternalTokenItem(
modifier = modifier,
onClick = content.onClick,
name = content.name,
tokenIconUrl = content.tokenIconUrl,
tokenIconResId = content.tokenIconResId,
@ -217,8 +218,12 @@ private fun InternalTokenItem(
hasPending: Boolean,
options: @Composable ConstraintLayoutScope.(ref: ConstrainedLayoutReference) -> Unit,
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
) {
BaseSurface(modifier) {
BaseSurface(
modifier = modifier,
onClick = onClick,
) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()

View file

@ -35,6 +35,7 @@ internal sealed interface TokenItemState {
val amount: String,
val hasPending: Boolean,
val tokenOptions: TokenOptionsState,
val onClick: () -> Unit,
) : TokenItemState
/**

View file

@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.hilt.navigation.compose.hiltViewModel
@ -16,12 +17,14 @@ import androidx.navigation.navArgument
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.NavigationStateHolder
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.WalletFragment
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import kotlin.properties.Delegates
/** Default implementation of wallet feature router */
@ -42,7 +45,7 @@ internal class DefaultWalletRouter(private val navigationStateHolder: Navigation
) {
composable(WalletRoute.Wallet.route) {
val viewModel = hiltViewModel<WalletViewModel>().apply { router = this@DefaultWalletRouter }
LocalLifecycleOwner.current.lifecycle.addObserver(observer = viewModel)
LocalLifecycleOwner.current.lifecycle.addObserver(viewModel)
WalletScreen(state = viewModel.uiState)
}
@ -96,6 +99,16 @@ internal class DefaultWalletRouter(private val navigationStateHolder: Navigation
navigationStateHolder.navigate(action = NavigationAction.OpenUrl(url))
}
override fun openTokenDetails(currency: CryptoCurrency) {
navigationStateHolder.navigate(
action = NavigationAction.NavigateTo(
screen = AppScreen.WalletDetails,
// TODO: [REDACTED_JIRA]
bundle = bundleOf(TokenDetailsRouter.SELECTED_CURRENCY_KEY to currency),
),
)
}
private companion object {
const val BACKSTACK_ENTRY_COUNT_TO_CLOSE_WALLET_SCREEN = 2
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.router
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.fragment.app.FragmentManager
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.wallet.navigation.WalletRouter
@ -40,4 +41,7 @@ internal interface InnerWalletRouter : WalletRouter {
/** Open transaction history website by [url] */
fun openTxHistoryWebsite(url: String)
/** Open token details screen */
fun openTokenDetails(currency: CryptoCurrency)
}

View file

@ -9,12 +9,14 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
internal class CryptoCurrencyStatusToTokenItemConverter(
private val appCurrencyProvider: Provider<AppCurrency>,
private val isWalletContentHidden: Boolean,
private val clickIntents: WalletClickIntents,
) : Converter<CryptoCurrencyStatus, TokenItemState> {
private val CryptoCurrencyStatus.networkIconResId: Int?
@ -61,6 +63,7 @@ internal class CryptoCurrencyStatusToTokenItemConverter(
priceChange = getPriceChangeConfig(),
)
},
onClick = { clickIntents.onTokenClick(currency) },
)
}

View file

@ -24,6 +24,7 @@ internal class TokenListToContentItemsConverter(
private val tokenStatusConverter = CryptoCurrencyStatusToTokenItemConverter(
isWalletContentHidden = isWalletContentHidden,
appCurrencyProvider = appCurrencyProvider,
clickIntents = clickIntents,
)
override fun convert(value: TokenList): WalletTokensListState {

View file

@ -1,5 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import com.tangem.domain.tokens.models.CryptoCurrency
internal interface WalletClickIntents {
fun onBackClick()
@ -37,4 +39,6 @@ internal interface WalletClickIntents {
fun onUnlockWalletNotificationClick()
fun onBottomSheetDismiss()
fun onTokenClick(currency: CryptoCurrency)
}

View file

@ -22,6 +22,7 @@ import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase
import com.tangem.domain.tokens.GetPrimaryCurrencyUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.tokens.models.Network
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
@ -439,6 +440,10 @@ internal class WalletViewModel @Inject constructor(
uiState = stateFactory.getStateWithClosedBottomSheet()
}
override fun onTokenClick(currency: CryptoCurrency) {
router.openTokenDetails(currency = currency)
}
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
return getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->