Updated on 2026-08-14
This commit is contained in:
parent
ef19a3697d
commit
db4efb71a1
15 changed files with 595 additions and 314 deletions
|
|
@ -5,116 +5,126 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
|
||||
internal object MockTokens {
|
||||
|
||||
val token1 = Token(
|
||||
id = Token.ID("token1"),
|
||||
networkId = MockNetworks.network1.id,
|
||||
name = "Token 1",
|
||||
symbol = "T1",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = null,
|
||||
derivationPath = null,
|
||||
)
|
||||
val token2 = Token(
|
||||
id = Token.ID("token2"),
|
||||
networkId = MockNetworks.network1.id,
|
||||
name = "Token 2",
|
||||
symbol = "T2",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token3 = Token(
|
||||
id = Token.ID("token3"),
|
||||
networkId = MockNetworks.network1.id,
|
||||
name = "Token 3",
|
||||
symbol = "T3",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token4 = Token(
|
||||
id = Token.ID("token4"),
|
||||
networkId = MockNetworks.network2.id,
|
||||
name = "Token 4",
|
||||
symbol = "T4",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = null,
|
||||
derivationPath = null,
|
||||
)
|
||||
val token5 = Token(
|
||||
id = Token.ID("token5"),
|
||||
networkId = MockNetworks.network2.id,
|
||||
name = "Token 5",
|
||||
symbol = "T5",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token6 = Token(
|
||||
id = Token.ID("token6"),
|
||||
networkId = MockNetworks.network2.id,
|
||||
name = "Token 6",
|
||||
symbol = "T6",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token7 = Token(
|
||||
id = Token.ID("token7"),
|
||||
networkId = MockNetworks.network3.id,
|
||||
name = "Token 7",
|
||||
symbol = "T7",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = null,
|
||||
derivationPath = null,
|
||||
)
|
||||
val token8 = Token(
|
||||
id = Token.ID("token8"),
|
||||
networkId = MockNetworks.network3.id,
|
||||
name = "Token 8",
|
||||
symbol = "T8",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token9 = Token(
|
||||
id = Token.ID("token9"),
|
||||
networkId = MockNetworks.network3.id,
|
||||
name = "Token 9",
|
||||
symbol = "T9",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token10 = Token(
|
||||
id = Token.ID("token10"),
|
||||
networkId = MockNetworks.network3.id,
|
||||
name = "Token 10",
|
||||
symbol = "T10",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token1
|
||||
get() = Token(
|
||||
id = Token.ID("token1"),
|
||||
networkId = MockNetworks.network1.id,
|
||||
name = "Token 1",
|
||||
symbol = "T1",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = null,
|
||||
derivationPath = null,
|
||||
)
|
||||
val token2
|
||||
get() = Token(
|
||||
id = Token.ID("token2"),
|
||||
networkId = MockNetworks.network1.id,
|
||||
name = "Token 2",
|
||||
symbol = "T2",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token3
|
||||
get() = Token(
|
||||
id = Token.ID("token3"),
|
||||
networkId = MockNetworks.network1.id,
|
||||
name = "Token 3",
|
||||
symbol = "T3",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token4
|
||||
get() = Token(
|
||||
id = Token.ID("token4"),
|
||||
networkId = MockNetworks.network2.id,
|
||||
name = "Token 4",
|
||||
symbol = "T4",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = null,
|
||||
derivationPath = null,
|
||||
)
|
||||
val token5
|
||||
get() = Token(
|
||||
id = Token.ID("token5"),
|
||||
networkId = MockNetworks.network2.id,
|
||||
name = "Token 5",
|
||||
symbol = "T5",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token6
|
||||
get() = Token(
|
||||
id = Token.ID("token6"),
|
||||
networkId = MockNetworks.network2.id,
|
||||
name = "Token 6",
|
||||
symbol = "T6",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token7
|
||||
get() = Token(
|
||||
id = Token.ID("token7"),
|
||||
networkId = MockNetworks.network3.id,
|
||||
name = "Token 7",
|
||||
symbol = "T7",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = null,
|
||||
derivationPath = null,
|
||||
)
|
||||
val token8
|
||||
get() = Token(
|
||||
id = Token.ID("token8"),
|
||||
networkId = MockNetworks.network3.id,
|
||||
name = "Token 8",
|
||||
symbol = "T8",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token9
|
||||
get() = Token(
|
||||
id = Token.ID("token9"),
|
||||
networkId = MockNetworks.network3.id,
|
||||
name = "Token 9",
|
||||
symbol = "T9",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
val token10
|
||||
get() = Token(
|
||||
id = Token.ID("token10"),
|
||||
networkId = MockNetworks.network3.id,
|
||||
name = "Token 10",
|
||||
symbol = "T10",
|
||||
isCustom = false,
|
||||
decimals = 8,
|
||||
iconUrl = null,
|
||||
contractAddress = "address",
|
||||
derivationPath = null,
|
||||
)
|
||||
|
||||
val tokens
|
||||
get() = mapOf(
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ dependencies {
|
|||
/** Domain modules */
|
||||
implementation(projects.common)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.settings)
|
||||
|
|
|
|||
|
|
@ -74,11 +74,15 @@ internal class DefaultWalletRouter(private val navigationStateHolder: Navigation
|
|||
}
|
||||
|
||||
override fun openOrganizeTokensScreen() {
|
||||
navController.navigate(WalletScreens.ORGANIZE_TOKENS.name)
|
||||
navController.navigate(route = WalletScreens.ORGANIZE_TOKENS.name)
|
||||
}
|
||||
|
||||
override fun openDetailsScreen() {
|
||||
navigationStateHolder.navigate(NavigationAction.NavigateTo(AppScreen.Details))
|
||||
navigationStateHolder.navigate(action = NavigationAction.NavigateTo(AppScreen.Details))
|
||||
}
|
||||
|
||||
override fun openOnboardingScreen() {
|
||||
navigationStateHolder.navigate(action = NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -33,4 +33,7 @@ internal interface InnerWalletRouter : WalletRouter {
|
|||
|
||||
/** Open details screen */
|
||||
fun openDetailsScreen()
|
||||
|
||||
/** Open onboarding screen */
|
||||
fun openOnboardingScreen()
|
||||
}
|
||||
|
|
@ -29,10 +29,14 @@ internal data class WalletBottomSheetConfig(
|
|||
@DrawableRes open val iconResId: Int,
|
||||
open val tint: Color? = null,
|
||||
val primaryButtonConfig: ButtonConfig,
|
||||
val secondaryButtonConfig: ButtonConfig? = null,
|
||||
val secondaryButtonConfig: ButtonConfig,
|
||||
) {
|
||||
|
||||
data class ButtonConfig(val text: String, val onClick: () -> Unit, @DrawableRes val iconResId: Int? = null)
|
||||
data class ButtonConfig(
|
||||
val text: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
@DrawableRes val iconResId: Int? = null,
|
||||
)
|
||||
|
||||
data class UnlockWallets(
|
||||
val onUnlockClick: () -> Unit,
|
||||
|
|
@ -45,9 +49,9 @@ internal data class WalletBottomSheetConfig(
|
|||
),
|
||||
iconResId = R.drawable.ic_locked_24,
|
||||
tint = TangemColorPalette.Black,
|
||||
primaryButtonConfig = ButtonConfig(text = "Unlock", onClick = onUnlockClick),
|
||||
primaryButtonConfig = ButtonConfig(text = TextReference.Str(value = "Unlock"), onClick = onUnlockClick),
|
||||
secondaryButtonConfig = ButtonConfig(
|
||||
text = "Scan card",
|
||||
text = TextReference.Str(value = "Scan card"),
|
||||
onClick = onScanClick,
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
),
|
||||
|
|
@ -61,19 +65,35 @@ internal data class WalletBottomSheetConfig(
|
|||
subtitle = TextReference.Str(value = "How was your experience with our app? Let us know:"),
|
||||
iconResId = R.drawable.ic_star_24,
|
||||
tint = TangemColorPalette.Tangerine,
|
||||
primaryButtonConfig = ButtonConfig(text = "Rate the app", onClick = onRateTheAppClick),
|
||||
secondaryButtonConfig = ButtonConfig(text = "Share feedback", onClick = onShareClick),
|
||||
primaryButtonConfig = ButtonConfig(
|
||||
text = TextReference.Str(value = "Rate the app"),
|
||||
onClick = onRateTheAppClick,
|
||||
),
|
||||
secondaryButtonConfig = ButtonConfig(
|
||||
text = TextReference.Str(value = "Share feedback"),
|
||||
onClick = onShareClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class MultiWalletAlreadySignedHashes(val onLearnClick: () -> Unit) : BottomSheetContentConfig(
|
||||
data class CriticalWarningAlreadySignedHashes(
|
||||
val onOkClick: () -> Unit,
|
||||
val onCancelClick: () -> Unit,
|
||||
) : BottomSheetContentConfig(
|
||||
title = TextReference.Res(
|
||||
id = R.string.warning_important_security_info,
|
||||
formatArgs = WrappedList(listOf("\u26A0")),
|
||||
),
|
||||
subtitle = TextReference.Res(id = R.string.warning_signed_tx_previously),
|
||||
subtitle = TextReference.Res(id = R.string.alert_signed_hashes_message),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
tint = null,
|
||||
primaryButtonConfig = ButtonConfig(text = "Learn more", onClick = onLearnClick),
|
||||
primaryButtonConfig = ButtonConfig(
|
||||
text = TextReference.Res(id = R.string.common_ok),
|
||||
onClick = onOkClick,
|
||||
),
|
||||
secondaryButtonConfig = ButtonConfig(
|
||||
text = TextReference.Res(id = R.string.common_cancel),
|
||||
onClick = onCancelClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -81,26 +81,26 @@ sealed class WalletNotification(open val state: NotificationState) {
|
|||
)
|
||||
|
||||
/**
|
||||
* "Wallet already signed hashes" notification
|
||||
* "Already topped up and signed hashes" warning notification
|
||||
*
|
||||
* @property onClick lambda be invoked when notification is clicked
|
||||
* @property onClick lambda be invoked when notification's close button is clicked
|
||||
*/
|
||||
data class AlreadyToppedUpAndSignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification(
|
||||
state = NotificationState.Clickable(
|
||||
data class WarningAlreadySignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification(
|
||||
state = NotificationState.Closable(
|
||||
title = TextReference.Res(id = R.string.common_warning),
|
||||
subtitle = TextReference.Res(id = R.string.alert_card_signed_transactions),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
onClick = onClick,
|
||||
tint = null,
|
||||
onCloseClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* "Multi wallet already signed hashes" notification
|
||||
* "Already signed hashes" critical warning notification
|
||||
*
|
||||
* @property onClick lambda be invoked when notification is clicked
|
||||
*/
|
||||
data class SignedTransactionsInThePast(override val onClick: () -> Unit) : Clickable, WalletNotification(
|
||||
data class CriticalWarningAlreadySignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification(
|
||||
state = NotificationState.Clickable(
|
||||
title = TextReference.Res(
|
||||
id = R.string.warning_important_security_info,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.factory
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletLoadedTokensListConverter.LoadedTokensListModel
|
||||
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorToWalletStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Converter from loaded [TokenListError] or [TokenList] to [WalletStateHolder]
|
||||
*
|
||||
* @property currentStateProvider current ui state provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WalletLoadedTokensListConverter(
|
||||
private val currentStateProvider: Provider<WalletStateHolder>,
|
||||
) : Converter<LoadedTokensListModel, WalletStateHolder> {
|
||||
|
||||
private val tokenListStateConverter = TokenListToWalletStateConverter(
|
||||
currentState = currentStateProvider(),
|
||||
isWalletContentHidden = false, // TODO: [REDACTED_JIRA]
|
||||
fiatCurrencyCode = "USD", // TODO: [REDACTED_JIRA]
|
||||
fiatCurrencySymbol = "$", // TODO: [REDACTED_JIRA]
|
||||
)
|
||||
|
||||
private val tokenListErrorStateConverter = TokenListErrorToWalletStateConverter(
|
||||
currentState = currentStateProvider(),
|
||||
)
|
||||
|
||||
override fun convert(value: LoadedTokensListModel): WalletStateHolder {
|
||||
return value.tokenListEither.fold(
|
||||
ifLeft = tokenListErrorStateConverter::convert,
|
||||
ifRight = {
|
||||
tokenListStateConverter.convert(
|
||||
value = TokenListToWalletStateConverter.TokensListModel(
|
||||
tokenList = it,
|
||||
isRefreshing = value.isRefreshing,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
data class LoadedTokensListModel(
|
||||
val tokenListEither: Either<TokenListError, TokenList>,
|
||||
val isRefreshing: Boolean,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,85 +1,75 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.builder
|
||||
package com.tangem.feature.wallet.presentation.wallet.state.factory
|
||||
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickCallbacks
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class WalletStateFactory(
|
||||
private val routerProvider: Provider<InnerWalletRouter>,
|
||||
private val onScanCardClick: () -> Unit,
|
||||
private val onWalletChange: (Int) -> Unit,
|
||||
private val onRefreshSwipe: () -> Unit,
|
||||
) {
|
||||
/**
|
||||
* Converter from loaded list of [UserWallet] to skeleton state of screen [WalletStateHolder]
|
||||
*
|
||||
* @property clickCallbacks screen click callbacks
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WalletSkeletonStateConverter(
|
||||
private val clickCallbacks: WalletClickCallbacks,
|
||||
) : Converter<List<UserWallet>, WalletStateHolder> {
|
||||
|
||||
fun getInitialState(): WalletStateHolder = WalletStateHolder.Loading(onBackClick = ::onBackClick)
|
||||
|
||||
fun getContentState(wallets: List<UserWallet>): WalletStateHolder {
|
||||
val cardTypeResolver = requireNotNull(wallets.firstOrNull()).scanResponse.cardTypesResolver
|
||||
override fun convert(value: List<UserWallet>): WalletStateHolder {
|
||||
val cardTypeResolver = requireNotNull(value.firstOrNull()).scanResponse.cardTypesResolver
|
||||
|
||||
return if (cardTypeResolver.isMultiwalletAllowed()) {
|
||||
createMultiCurrencyState(wallets)
|
||||
createMultiCurrencyState(value)
|
||||
} else {
|
||||
createSingleCurrencyState(wallets)
|
||||
createSingleCurrencyState(value, cardTypeResolver)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMultiCurrencyState(wallets: List<UserWallet>): WalletStateHolder.MultiCurrencyContent {
|
||||
return WalletStateHolder.MultiCurrencyContent(
|
||||
onBackClick = ::onBackClick,
|
||||
onBackClick = clickCallbacks::onBackClick,
|
||||
topBarConfig = createTopBarConfig(),
|
||||
walletsListConfig = createWalletsListConfig(wallets),
|
||||
pullToRefreshConfig = createPullToRefreshConfig(),
|
||||
contentItems = persistentListOf(),
|
||||
notifications = persistentListOf(),
|
||||
bottomSheet = WalletBottomSheetConfig(
|
||||
// TODO: check notifications
|
||||
isShow = false,
|
||||
onDismissRequest = {},
|
||||
content = WalletBottomSheetConfig.BottomSheetContentConfig.LikeTangemApp(
|
||||
onRateTheAppClick = {},
|
||||
onShareClick = {},
|
||||
),
|
||||
),
|
||||
onOrganizeTokensClick = routerProvider()::openOrganizeTokensScreen,
|
||||
bottomSheet = null,
|
||||
onOrganizeTokensClick = clickCallbacks::onOrganizeTokensClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSingleCurrencyState(wallets: List<UserWallet>): WalletStateHolder.SingleCurrencyContent {
|
||||
private fun createSingleCurrencyState(
|
||||
wallets: List<UserWallet>,
|
||||
cardTypeResolver: CardTypesResolver,
|
||||
): WalletStateHolder.SingleCurrencyContent {
|
||||
return WalletStateHolder.SingleCurrencyContent(
|
||||
onBackClick = ::onBackClick,
|
||||
onBackClick = clickCallbacks::onBackClick,
|
||||
topBarConfig = createTopBarConfig(),
|
||||
walletsListConfig = createWalletsListConfig(wallets),
|
||||
pullToRefreshConfig = createPullToRefreshConfig(),
|
||||
contentItems = persistentListOf(),
|
||||
notifications = persistentListOf(),
|
||||
bottomSheet = WalletBottomSheetConfig(
|
||||
// TODO: check notifications
|
||||
isShow = false,
|
||||
onDismissRequest = {},
|
||||
content = WalletBottomSheetConfig.BottomSheetContentConfig.LikeTangemApp(
|
||||
onRateTheAppClick = {},
|
||||
onShareClick = {},
|
||||
),
|
||||
),
|
||||
bottomSheet = null,
|
||||
buttons = WalletPreviewData.singleWalletScreenState.buttons, // TODO: create buttons
|
||||
// TODO: create market price block
|
||||
marketPriceBlockState = WalletPreviewData.singleWalletScreenState.marketPriceBlockState,
|
||||
marketPriceBlockState = MarketPriceBlockState.Loading(
|
||||
currencyName = cardTypeResolver.getBlockchain().currency,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onBackClick() = routerProvider().popBackStack()
|
||||
|
||||
private fun createTopBarConfig(): WalletTopBarConfig {
|
||||
return WalletTopBarConfig(
|
||||
onScanCardClick = onScanCardClick,
|
||||
onMoreClick = routerProvider()::openDetailsScreen,
|
||||
onScanCardClick = clickCallbacks::onScanCardClick,
|
||||
onMoreClick = clickCallbacks::onDetailsClick,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -88,22 +78,22 @@ internal class WalletStateFactory(
|
|||
selectedWalletIndex = 0,
|
||||
wallets = wallets.map { wallet ->
|
||||
WalletCardState.Loading(
|
||||
id = if (wallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) { // TODO
|
||||
// TODO remove after adding release logic for GetTokenListUseCase
|
||||
id = if (wallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
UserWalletId("123")
|
||||
} else {
|
||||
UserWalletId("321")
|
||||
},
|
||||
// TODO: wallet.walletId,
|
||||
title = wallet.name,
|
||||
additionalInfo = "", // TODO
|
||||
additionalInfo = "", // TODO add additional info resolver
|
||||
imageResId = WalletImageResolver.resolve(cardTypesResolver = wallet.scanResponse.cardTypesResolver),
|
||||
)
|
||||
}.toImmutableList(),
|
||||
onWalletChange = onWalletChange,
|
||||
onWalletChange = clickCallbacks::onWalletChange,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createPullToRefreshConfig(): WalletPullToRefreshConfig {
|
||||
return WalletPullToRefreshConfig(isRefreshing = false, onRefresh = onRefreshSwipe)
|
||||
return WalletPullToRefreshConfig(isRefreshing = false, onRefresh = clickCallbacks::onRefreshSwipe)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.factory
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletBottomSheetConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletLoadedTokensListConverter.LoadedTokensListModel
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickCallbacks
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* Main factory for creating [WalletStateHolder]
|
||||
*
|
||||
* @property currentStateProvider current ui state provider
|
||||
* @property clickCallbacks screen click callbacks
|
||||
*/
|
||||
internal class WalletStateFactory(
|
||||
private val currentStateProvider: Provider<WalletStateHolder>,
|
||||
private val clickCallbacks: WalletClickCallbacks,
|
||||
) {
|
||||
|
||||
private val skeletonConverter = WalletSkeletonStateConverter(clickCallbacks = clickCallbacks)
|
||||
private val loadedTokensListConverter = WalletLoadedTokensListConverter(currentStateProvider = currentStateProvider)
|
||||
|
||||
fun getInitialState(): WalletStateHolder = WalletStateHolder.Loading(onBackClick = clickCallbacks::onBackClick)
|
||||
|
||||
fun getSkeletonState(wallets: List<UserWallet>): WalletStateHolder = skeletonConverter.convert(wallets)
|
||||
|
||||
fun getStateByTokensList(
|
||||
tokenListEither: Either<TokenListError, TokenList>,
|
||||
isRefreshing: Boolean,
|
||||
): WalletStateHolder {
|
||||
return loadedTokensListConverter.convert(
|
||||
value = LoadedTokensListModel(tokenListEither = tokenListEither, isRefreshing = isRefreshing),
|
||||
)
|
||||
}
|
||||
|
||||
fun getStateByNotifications(notifications: ImmutableList<WalletNotification>): WalletStateHolder {
|
||||
return currentStateProvider().copySealed(notifications = notifications)
|
||||
}
|
||||
|
||||
fun getStateAfterWalletChanging(index: Int): WalletStateHolder {
|
||||
return currentStateProvider().let { stateHolder ->
|
||||
stateHolder.copySealed(walletsListConfig = stateHolder.walletsListConfig.copy(selectedWalletIndex = index))
|
||||
}
|
||||
}
|
||||
|
||||
fun getStateAfterContentRefreshing(): WalletStateHolder {
|
||||
return currentStateProvider().let { state ->
|
||||
state.copySealed(pullToRefreshConfig = state.pullToRefreshConfig.copy(isRefreshing = true))
|
||||
}
|
||||
}
|
||||
|
||||
fun getStateWithOpenBottomSheet(content: WalletBottomSheetConfig.BottomSheetContentConfig): WalletStateHolder {
|
||||
return currentStateProvider().let { state ->
|
||||
state.copySealed(
|
||||
bottomSheet = WalletBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = { state.copySealed(bottomSheet = state.bottomSheet?.copy(isShow = false)) },
|
||||
content = content,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -81,11 +81,10 @@ private fun BottomSheetContent(config: WalletBottomSheetConfig.BottomSheetConten
|
|||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing10)) {
|
||||
val buttonModifier = Modifier.fillMaxWidth()
|
||||
|
||||
PrimaryButton(config = config.primaryButtonConfig, modifier = buttonModifier)
|
||||
|
||||
if (config.secondaryButtonConfig != null) {
|
||||
SecondaryButton(config = config.secondaryButtonConfig, modifier = buttonModifier)
|
||||
}
|
||||
SecondaryButton(config = config.secondaryButtonConfig, modifier = buttonModifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -97,13 +96,13 @@ private fun PrimaryButton(
|
|||
) {
|
||||
if (config.iconResId == null) {
|
||||
PrimaryButton(
|
||||
text = config.text,
|
||||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
PrimaryButtonIconStart(
|
||||
text = config.text,
|
||||
text = config.text.resolveReference(),
|
||||
iconResId = config.iconResId,
|
||||
onClick = config.onClick,
|
||||
modifier = modifier,
|
||||
|
|
@ -118,13 +117,13 @@ private fun SecondaryButton(
|
|||
) {
|
||||
if (config.iconResId == null) {
|
||||
SecondaryButton(
|
||||
text = config.text,
|
||||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
SecondaryButtonIconStart(
|
||||
text = config.text,
|
||||
text = config.text.resolveReference(),
|
||||
iconResId = config.iconResId,
|
||||
onClick = config.onClick,
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||
|
||||
import com.tangem.core.ui.components.transactions.TransactionState
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder.MultiCurrencyContent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder.SingleCurrencyContent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletsListConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter.TokensListModel
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
|
|
@ -13,47 +16,62 @@ internal class TokenListToWalletStateConverter(
|
|||
private val isWalletContentHidden: Boolean,
|
||||
private val fiatCurrencyCode: String,
|
||||
private val fiatCurrencySymbol: String,
|
||||
) : Converter<TokenList, WalletStateHolder> {
|
||||
) : Converter<TokensListModel, WalletStateHolder> {
|
||||
|
||||
override fun convert(value: TokenList): WalletStateHolder {
|
||||
return when (currentState) {
|
||||
is MultiCurrencyContent -> currentState.updateWithTokenList(value)
|
||||
is SingleCurrencyContent -> currentState.updateWithTokenList(value)
|
||||
is WalletStateHolder.Loading,
|
||||
is WalletStateHolder.UnlockWalletContent,
|
||||
-> currentState
|
||||
private val tokenListToContentConverter = TokenListToContentItemsConverter(
|
||||
isWalletContentHidden = isWalletContentHidden,
|
||||
fiatCurrencyCode = fiatCurrencyCode,
|
||||
fiatCurrencySymbol = fiatCurrencySymbol,
|
||||
)
|
||||
|
||||
override fun convert(value: TokensListModel): WalletStateHolder {
|
||||
val state = if (currentState is MultiCurrencyContent) {
|
||||
currentState.updateWithTokenList(tokenList = value.tokenList)
|
||||
} else {
|
||||
currentState
|
||||
}
|
||||
|
||||
return state.copySealed(
|
||||
walletsListConfig = state.updateSelectedWallet(value.tokenList.totalFiatBalance),
|
||||
pullToRefreshConfig = if (value.isRefreshing) {
|
||||
state.pullToRefreshConfig.copy(isRefreshing = state.getRefreshingStatus())
|
||||
} else {
|
||||
state.pullToRefreshConfig
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun MultiCurrencyContent.updateWithTokenList(tokenList: TokenList): MultiCurrencyContent {
|
||||
val converter = TokenListToContentItemsConverter(isWalletContentHidden, fiatCurrencyCode, fiatCurrencySymbol)
|
||||
|
||||
return this.copy(
|
||||
walletsListConfig = updateSelectedWallet(tokenList.totalFiatBalance),
|
||||
contentItems = converter.convert(tokenList),
|
||||
)
|
||||
}
|
||||
|
||||
private fun SingleCurrencyContent.updateWithTokenList(tokenList: TokenList): SingleCurrencyContent {
|
||||
return this.copy(
|
||||
walletsListConfig = updateSelectedWallet(tokenList.totalFiatBalance),
|
||||
)
|
||||
return this.copy(contentItems = tokenListToContentConverter.convert(value = tokenList))
|
||||
}
|
||||
|
||||
private fun WalletStateHolder.updateSelectedWallet(fiatBalance: TokenList.FiatBalance): WalletsListConfig {
|
||||
val selectedWalletIndex = walletsListConfig.selectedWalletIndex
|
||||
val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex]
|
||||
val converter = FiatBalanceToWalletCardConverter(
|
||||
selectedWalletCard,
|
||||
isWalletContentHidden,
|
||||
fiatCurrencyCode,
|
||||
fiatCurrencySymbol,
|
||||
currentState = selectedWalletCard,
|
||||
isWalletContentHidden = isWalletContentHidden,
|
||||
fiatCurrencyCode = fiatCurrencyCode,
|
||||
fiatCurrencySymbol = fiatCurrencySymbol,
|
||||
)
|
||||
|
||||
return walletsListConfig.copy(
|
||||
wallets = walletsListConfig.wallets
|
||||
.toPersistentList()
|
||||
.set(selectedWalletIndex, converter.convert(fiatBalance)),
|
||||
.set(index = selectedWalletIndex, element = converter.convert(fiatBalance)),
|
||||
)
|
||||
}
|
||||
|
||||
private fun WalletStateHolder.getRefreshingStatus(): Boolean {
|
||||
return contentItems.any { state ->
|
||||
val isMultiCurrencyItem = state as? WalletContentItemState.MultiCurrencyItem.Token
|
||||
val isSingleCurrencyItem = state as? WalletContentItemState.SingleCurrencyItem.Transaction
|
||||
|
||||
isMultiCurrencyItem?.state is TokenItemState.Loading ||
|
||||
isSingleCurrencyItem?.state is TransactionState.Loading ||
|
||||
state is WalletContentItemState.Loading
|
||||
}
|
||||
}
|
||||
|
||||
data class TokensListModel(val tokenList: TokenList, val isRefreshing: Boolean)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
/**
|
||||
* Job holder. It is automatically finished old job if new one is started
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class JobHolder {
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
/** Update current job */
|
||||
fun update(job: Job) {
|
||||
this.job?.cancel()
|
||||
this.job = job
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Job.saveIn(jobHolder: JobHolder) = jobHolder.update(job = this)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels
|
||||
|
||||
internal interface WalletClickCallbacks {
|
||||
|
||||
fun onBackClick()
|
||||
|
||||
fun onScanCardClick()
|
||||
|
||||
fun onDetailsClick()
|
||||
|
||||
fun onBackupCardClick()
|
||||
|
||||
fun onCriticalWarningAlreadySignedHashesClick()
|
||||
|
||||
fun onCloseWarningAlreadySignedHashesClick()
|
||||
|
||||
fun onLikeTangemAppClick()
|
||||
|
||||
fun onRateTheAppClick()
|
||||
|
||||
fun onShareClick()
|
||||
|
||||
fun onWalletChange(index: Int)
|
||||
|
||||
fun onRefreshSwipe()
|
||||
|
||||
fun onOrganizeTokensClick()
|
||||
}
|
||||
|
|
@ -1,27 +1,38 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels
|
||||
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.tokens.model.NetworkGroup
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.tokens.model.TokenStatus
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
/**
|
||||
* Wallet notifications list factory
|
||||
*
|
||||
* @property wasCardScannedCallback callback that check if card was scanned
|
||||
* @property isUserAlreadyRateAppCallback callback that check if card is user already rate app
|
||||
* @property isDemoCardCallback callback that check if card is demo
|
||||
* @property clickCallbacks screen click callbacks
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
internal class WalletNotificationsListFactory(
|
||||
private val currentStateProvider: Provider<WalletStateHolder>,
|
||||
private val wasCardScannedCallback: suspend (String) -> Boolean,
|
||||
private val isUserAlreadyRateAppCallback: suspend () -> Boolean,
|
||||
private val onWalletAlreadySignedHashesClick: () -> Unit,
|
||||
private val onMultiWalletAlreadySignedHashesClick: () -> Unit,
|
||||
private val onBackupCardClick: () -> Unit,
|
||||
private val hasUnreachableNetworksCallback: () -> Boolean,
|
||||
private val onLikeTangemAppClick: () -> Unit,
|
||||
private val isDemoCardCallback: (String) -> Boolean,
|
||||
private val clickCallbacks: WalletClickCallbacks,
|
||||
) {
|
||||
|
||||
fun create(cardTypesResolver: CardTypesResolver): Flow<ImmutableList<WalletNotification>> {
|
||||
fun create(cardTypesResolver: CardTypesResolver, tokenList: TokenList): Flow<ImmutableList<WalletNotification>> {
|
||||
// TODO: [REDACTED_JIRA] order
|
||||
return flow {
|
||||
emit(
|
||||
|
|
@ -33,31 +44,31 @@ internal class WalletNotificationsListFactory(
|
|||
|
||||
addRemainingSignaturesLeftNotifications(cardTypesResolver)
|
||||
|
||||
val isDemo = isDemoCardCallback(cardTypesResolver.getCardId())
|
||||
if (!cardTypesResolver.isReleaseFirmwareType()) {
|
||||
add(element = WalletNotification.DevCard)
|
||||
} else {
|
||||
addReleaseSpecialNotifications(cardTypesResolver)
|
||||
addReleaseSpecialNotifications(cardTypesResolver = cardTypesResolver, isDemo = isDemo)
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_JIRA] - DemoModeDatasource
|
||||
if (isDemo) {
|
||||
add(element = WalletNotification.DemoCard)
|
||||
}
|
||||
|
||||
if (hasUnreachableNetworksCallback()) {
|
||||
if (hasUnreachableNetworks()) {
|
||||
add(element = WalletNotification.UnreachableNetworks)
|
||||
}
|
||||
|
||||
if (!cardTypesResolver.isBackupForbidden() && !cardTypesResolver.hasBackup()) {
|
||||
add(element = WalletNotification.BackupCard(onClick = onBackupCardClick))
|
||||
add(element = WalletNotification.BackupCard(onClick = clickCallbacks::onBackupCardClick))
|
||||
}
|
||||
|
||||
/* TODO: [REDACTED_JIRA]
|
||||
* List<WalletStoreModel>
|
||||
* .filter { store ->
|
||||
* store.walletsData.any { it.status is WalletDataModel.MissedDerivation }
|
||||
* }
|
||||
*/
|
||||
if (tokenList.hasMissedDerivations()) {
|
||||
add(element = WalletNotification.ScanCard(onClick = clickCallbacks::onScanCardClick))
|
||||
}
|
||||
|
||||
if (isUserAlreadyRateAppCallback()) {
|
||||
add(element = WalletNotification.LikeTangemApp(onClick = onLikeTangemAppClick))
|
||||
add(element = WalletNotification.LikeTangemApp(onClick = clickCallbacks::onLikeTangemAppClick))
|
||||
}
|
||||
}.toImmutableList(),
|
||||
)
|
||||
|
|
@ -75,19 +86,21 @@ internal class WalletNotificationsListFactory(
|
|||
|
||||
private suspend fun MutableList<WalletNotification>.addReleaseSpecialNotifications(
|
||||
cardTypesResolver: CardTypesResolver,
|
||||
isDemo: Boolean,
|
||||
) {
|
||||
// TODO: [REDACTED_JIRA] !isDemo
|
||||
if (!wasCardScannedCallback(cardTypesResolver.getCardId()) && cardTypesResolver.isMultiwalletAllowed()) {
|
||||
if (!wasCardScannedCallback(cardTypesResolver.getCardId()) && cardTypesResolver.isMultiwalletAllowed() &&
|
||||
!isDemo
|
||||
) {
|
||||
if (cardTypesResolver.isBackupForbidden() && cardTypesResolver.hasWalletSignedHashes()) {
|
||||
add(
|
||||
element = WalletNotification.SignedTransactionsInThePast(
|
||||
onClick = onMultiWalletAlreadySignedHashesClick,
|
||||
element = WalletNotification.CriticalWarningAlreadySignedHashes(
|
||||
onClick = clickCallbacks::onCriticalWarningAlreadySignedHashesClick,
|
||||
),
|
||||
)
|
||||
} else if (cardTypesResolver.hasWalletSignedHashes()) {
|
||||
add(
|
||||
element = WalletNotification.AlreadyToppedUpAndSignedHashes(
|
||||
onClick = onWalletAlreadySignedHashesClick,
|
||||
element = WalletNotification.WarningAlreadySignedHashes(
|
||||
onClick = clickCallbacks::onCloseWarningAlreadySignedHashesClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -98,6 +111,26 @@ internal class WalletNotificationsListFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private fun hasUnreachableNetworks(): Boolean {
|
||||
val isUnreachableState = { item: WalletContentItemState.MultiCurrencyItem ->
|
||||
(item as? WalletContentItemState.MultiCurrencyItem.Token)?.state is TokenItemState.Unreachable
|
||||
}
|
||||
|
||||
return currentStateProvider().let { state ->
|
||||
state is WalletStateHolder.MultiCurrencyContent && state.contentItems.any(isUnreachableState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TokenList.hasMissedDerivations(): Boolean {
|
||||
val statuses = when (this) {
|
||||
is TokenList.GroupedByNetwork -> groups.flatMap(NetworkGroup::tokens).map(TokenStatus::value)
|
||||
is TokenList.Ungrouped -> tokens.map(TokenStatus::value)
|
||||
TokenList.NotInitialized -> emptyList()
|
||||
}
|
||||
|
||||
return statuses.any { it is TokenStatus.MissedDerivation }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_REMAINING_SIGNATURES_COUNT = 10
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,30 +4,25 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.*
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.core.ui.components.transactions.TransactionState
|
||||
import com.tangem.domain.card.GetAccessCodeSavingStatusUseCase
|
||||
import com.tangem.domain.card.GetBiometricsStatusUseCase
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.SetAccessCodeRequestPolicyUseCase
|
||||
import com.tangem.domain.card.*
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.userwallets.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.builder.WalletStateFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorToWalletStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletStateFactory
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -41,74 +36,106 @@ import kotlin.properties.Delegates
|
|||
@Suppress("LongParameterList")
|
||||
@HiltViewModel
|
||||
internal class WalletViewModel @Inject constructor(
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val getBiometricsStatusUseCase: GetBiometricsStatusUseCase,
|
||||
private val setAccessCodeRequestPolicyUseCase: SetAccessCodeRequestPolicyUseCase,
|
||||
private val getAccessCodeSavingStatusUseCase: GetAccessCodeSavingStatusUseCase,
|
||||
private val getTokenListUseCase: GetTokenListUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val getCardWasScannedUseCase: GetCardWasScannedUseCase,
|
||||
private val isUserAlreadyRateAppUseCase: IsUserAlreadyRateAppUseCase,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
) : ViewModel(), DefaultLifecycleObserver, WalletClickCallbacks {
|
||||
|
||||
/** Feature router */
|
||||
var router: InnerWalletRouter by Delegates.notNull()
|
||||
|
||||
private val notificationsListFactory = WalletNotificationsListFactory(
|
||||
currentStateProvider = Provider { uiState },
|
||||
wasCardScannedCallback = getCardWasScannedUseCase::invoke,
|
||||
isUserAlreadyRateAppCallback = isUserAlreadyRateAppUseCase::invoke,
|
||||
isDemoCardCallback = isDemoCardUseCase::invoke,
|
||||
clickCallbacks = this,
|
||||
)
|
||||
|
||||
private val stateFactory = WalletStateFactory(
|
||||
routerProvider = Provider { router },
|
||||
onScanCardClick = ::onScanCardClick,
|
||||
onWalletChange = ::changeWallet,
|
||||
onRefreshSwipe = ::refreshContent,
|
||||
currentStateProvider = Provider { uiState },
|
||||
clickCallbacks = this,
|
||||
)
|
||||
|
||||
/** Screen state */
|
||||
var uiState by mutableStateOf(stateFactory.getInitialState())
|
||||
var uiState: WalletStateHolder by mutableStateOf(stateFactory.getInitialState())
|
||||
private set
|
||||
|
||||
private var cardTypeResolverMap: Map<UserWalletId, CardTypesResolver> by Delegates.notNull()
|
||||
|
||||
private val getTokensJobHolder = JobHolder()
|
||||
private val getNotificationJobHolder = JobHolder()
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
getWalletsUseCase()
|
||||
.distinctUntilChanged()
|
||||
.flowWithLifecycle(owner.lifecycle)
|
||||
.distinctUntilChanged()
|
||||
.onEach { wallets ->
|
||||
if (wallets.isEmpty()) return@onEach
|
||||
|
||||
uiState = stateFactory.getContentState(wallets = wallets)
|
||||
updateContentItems()
|
||||
cardTypeResolverMap = wallets.associate { wallet ->
|
||||
// TODO remove after adding release logic for GetTokenListUseCase
|
||||
if (wallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
UserWalletId("123")
|
||||
} else {
|
||||
UserWalletId("321")
|
||||
} to wallet.scanResponse.cardTypesResolver
|
||||
}
|
||||
|
||||
uiState = stateFactory.getSkeletonState(wallets = wallets)
|
||||
|
||||
updateContentItems(index = 0)
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun updateContentItems() {
|
||||
getTokenListUseCase(
|
||||
userWalletId = uiState.walletsListConfig.wallets.get(
|
||||
index = uiState.walletsListConfig.selectedWalletIndex,
|
||||
).id,
|
||||
private fun updateContentItems(index: Int, isRefreshing: Boolean = false) {
|
||||
// TODO: use GetTransactionsUseCase
|
||||
|
||||
getTokenListUseCase(userWalletId = uiState.walletsListConfig.wallets[index].id)
|
||||
.distinctUntilChanged()
|
||||
.onEach { tokenListEither ->
|
||||
uiState = stateFactory.getStateByTokensList(
|
||||
tokenListEither = tokenListEither,
|
||||
isRefreshing = isRefreshing,
|
||||
)
|
||||
|
||||
tokenListEither.onRight { updateNotifications(index = index, tokenList = it) }
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(viewModelScope)
|
||||
.saveIn(jobHolder = getTokensJobHolder)
|
||||
}
|
||||
|
||||
private fun updateNotifications(index: Int, tokenList: TokenList) {
|
||||
notificationsListFactory.create(
|
||||
cardTypesResolver = requireNotNull(
|
||||
value = cardTypeResolverMap[uiState.walletsListConfig.wallets[index].id],
|
||||
lazyMessage = {
|
||||
"CardTypeResolverMap doesn't contain this id = ${uiState.walletsListConfig.wallets[index].id}"
|
||||
},
|
||||
),
|
||||
tokenList = tokenList,
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.mapLatest(::updateStateWithTokenListOrError)
|
||||
.onEach {
|
||||
uiState = it.copySealed(
|
||||
pullToRefreshConfig = uiState.pullToRefreshConfig.copy(isRefreshing = getRefreshingStatus()),
|
||||
)
|
||||
}
|
||||
.onEach { uiState = stateFactory.getStateByNotifications(notifications = it) }
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(viewModelScope)
|
||||
.saveIn(jobHolder = getNotificationJobHolder)
|
||||
}
|
||||
|
||||
private fun getRefreshingStatus(): Boolean {
|
||||
return uiState.contentItems.any { state ->
|
||||
val isMultiCurrencyItem = state as? WalletContentItemState.MultiCurrencyItem.Token
|
||||
val isSingleCurrencyItem = state as? WalletContentItemState.SingleCurrencyItem.Transaction
|
||||
override fun onBackClick() = router.popBackStack()
|
||||
|
||||
isMultiCurrencyItem?.state is TokenItemState.Loading ||
|
||||
isSingleCurrencyItem?.state is TransactionState.Loading ||
|
||||
state is WalletContentItemState.Loading
|
||||
}
|
||||
}
|
||||
|
||||
private fun onScanCardClick() {
|
||||
override fun onScanCardClick() {
|
||||
val prevRequestPolicyStatus = getBiometricsStatusUseCase()
|
||||
|
||||
// Update access the code policy according access code saving status
|
||||
|
|
@ -138,47 +165,52 @@ internal class WalletViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun changeWallet(index: Int) {
|
||||
override fun onDetailsClick() = router.openDetailsScreen()
|
||||
|
||||
override fun onBackupCardClick() = router.openOnboardingScreen()
|
||||
|
||||
override fun onCriticalWarningAlreadySignedHashesClick() {
|
||||
uiState = stateFactory.getStateWithOpenBottomSheet(
|
||||
content = WalletBottomSheetConfig.BottomSheetContentConfig.CriticalWarningAlreadySignedHashes(
|
||||
onOkClick = {},
|
||||
onCancelClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCloseWarningAlreadySignedHashesClick() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
override fun onLikeTangemAppClick() {
|
||||
uiState = stateFactory.getStateWithOpenBottomSheet(
|
||||
content = WalletBottomSheetConfig.BottomSheetContentConfig.LikeTangemApp(
|
||||
onRateTheAppClick = ::onRateTheAppClick,
|
||||
onShareClick = ::onShareClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRateTheAppClick() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
override fun onShareClick() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
override fun onWalletChange(index: Int) {
|
||||
if (uiState.walletsListConfig.selectedWalletIndex == index) return
|
||||
|
||||
uiState = when (val state = uiState) {
|
||||
is WalletStateHolder.MultiCurrencyContent -> state.copy(
|
||||
walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index),
|
||||
)
|
||||
is WalletStateHolder.SingleCurrencyContent -> state.copy(
|
||||
walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index),
|
||||
)
|
||||
is WalletStateHolder.UnlockWalletContent -> state.copy(
|
||||
walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index),
|
||||
)
|
||||
is WalletStateHolder.Loading -> state
|
||||
}
|
||||
uiState = stateFactory.getStateAfterWalletChanging(index = index)
|
||||
|
||||
updateContentItems()
|
||||
updateContentItems(index = index)
|
||||
}
|
||||
|
||||
private fun refreshContent() {
|
||||
uiState = uiState.copySealed(pullToRefreshConfig = uiState.pullToRefreshConfig.copy(isRefreshing = true))
|
||||
updateContentItems()
|
||||
override fun onRefreshSwipe() {
|
||||
uiState = stateFactory.getStateAfterContentRefreshing()
|
||||
updateContentItems(index = uiState.walletsListConfig.selectedWalletIndex, isRefreshing = true)
|
||||
}
|
||||
|
||||
private fun updateStateWithTokenListOrError(tokenList: Either<TokenListError, TokenList>): WalletStateHolder {
|
||||
val updateStateWithError = { error: TokenListError ->
|
||||
val converter = TokenListErrorToWalletStateConverter(uiState)
|
||||
|
||||
converter.convert(error)
|
||||
}
|
||||
val updateState = { list: TokenList ->
|
||||
val converter = TokenListToWalletStateConverter(
|
||||
uiState,
|
||||
isWalletContentHidden = false, // TODO: [REDACTED_JIRA]
|
||||
fiatCurrencyCode = "USD", // TODO: [REDACTED_JIRA]
|
||||
fiatCurrencySymbol = "$", // TODO: [REDACTED_JIRA]
|
||||
)
|
||||
|
||||
converter.convert(list)
|
||||
}
|
||||
|
||||
return tokenList.fold(ifLeft = updateStateWithError, ifRight = updateState)
|
||||
}
|
||||
override fun onOrganizeTokensClick() = router.openOrganizeTokensScreen()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue