Updated on 2026-08-14
This commit is contained in:
commit
dbe235560b
428 changed files with 10165 additions and 3907 deletions
|
|
@ -4,36 +4,28 @@ import androidx.compose.runtime.Stable
|
|||
import arrow.core.getOrElse
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.core.deeplink.global.ReferralDeepLink
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.isMultiCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig
|
||||
|
|
@ -47,9 +39,11 @@ import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvid
|
|||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -73,7 +67,6 @@ internal class WalletModel @Inject constructor(
|
|||
private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender,
|
||||
private val walletDeepLinksHandler: WalletDeepLinksHandler,
|
||||
private val walletNameMigrationUseCase: WalletNameMigrationUseCase,
|
||||
private val refreshMultiCurrencyWalletQuotesUseCase: RefreshMultiCurrencyWalletQuotesUseCase,
|
||||
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
|
||||
|
|
@ -81,11 +74,11 @@ internal class WalletModel @Inject constructor(
|
|||
private val tokenListStore: MultiWalletTokenListStore,
|
||||
private val onrampStatusFactory: OnrampStatusFactory,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
private val appRouter: AppRouter,
|
||||
private val routingFeatureToggle: RoutingFeatureToggle,
|
||||
private val walletContentFetcher: WalletContentFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
private val observeAndClearNFTCacheIfNeedUseCase: ObserveAndClearNFTCacheIfNeedUseCase,
|
||||
private val walletDeepLinkActionListener: WalletDeepLinkActionListener,
|
||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||
val innerWalletRouter: InnerWalletRouter,
|
||||
) : Model() {
|
||||
|
|
@ -167,7 +160,16 @@ internal class WalletModel @Inject constructor(
|
|||
return innerWalletRouter.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase()
|
||||
}
|
||||
|
||||
private fun subscribeToUserWalletsUpdates() {
|
||||
private fun subscribeToUserWalletsUpdates() = channelFlow<Unit> {
|
||||
val firstWalletsUseCaseEmit = MutableStateFlow(false)
|
||||
suspend fun waitFirstWalletsUseCaseEmit() = firstWalletsUseCaseEmit.filter { it }.first()
|
||||
|
||||
// deepLinkActionFlow must wait for fist getWalletsUseCase() emit to correct handle Action.InitializeWallets
|
||||
walletDeepLinkActionListener.selectWalletFlow
|
||||
.onEach { waitFirstWalletsUseCaseEmit() }
|
||||
.onEach(::selectWalletById)
|
||||
.launchIn(this)
|
||||
|
||||
getWalletsUseCase()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
|
|
@ -178,10 +180,13 @@ internal class WalletModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
.onEach(::updateWallets)
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
.saveIn(walletsUpdateJobHolder)
|
||||
.onEach { firstWalletsUseCaseEmit.update { true } }
|
||||
.launchIn(this)
|
||||
awaitClose()
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(modelScope)
|
||||
.saveIn(walletsUpdateJobHolder)
|
||||
|
||||
private fun subscribeOnBalanceHiding() {
|
||||
getBalanceHidingSettingsUseCase()
|
||||
|
|
@ -226,12 +231,6 @@ internal class WalletModel @Inject constructor(
|
|||
selectedWalletAnalyticsSender.send(selectedWallet)
|
||||
}
|
||||
|
||||
if (!routingFeatureToggle.isDeepLinkNavigationEnabled) {
|
||||
// Registering here, because `WalletDeepLinksHandler` unregisters deeplink when scope is cancelled
|
||||
// This is temporary solution, will be removed with complete deeplink navigation overhaul
|
||||
addReferralDeepLink(selectedWallet)
|
||||
walletDeepLinksHandler.registerForWallet(scope = modelScope, userWallet = selectedWallet)
|
||||
}
|
||||
subscribeOnExpressTransactionsUpdates(selectedWallet)
|
||||
observeAndClearNFTCacheIfNeedUseCase(selectedWallet)
|
||||
}
|
||||
|
|
@ -240,18 +239,17 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun addReferralDeepLink(userWallet: UserWallet) {
|
||||
deepLinksRegistry.register(
|
||||
ReferralDeepLink(
|
||||
onReceive = {
|
||||
if (userWallet !is UserWallet.Cold || userWallet.cardTypesResolver.isTangemWallet()) {
|
||||
appRouter.push(
|
||||
AppRoute.ReferralProgram(userWalletId = userWallet.walletId),
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
private fun selectWalletById(selectedWalletId: UserWalletId) {
|
||||
val currentWalletId = stateHolder.getSelectedWalletId()
|
||||
|
||||
if (currentWalletId == selectedWalletId) return
|
||||
|
||||
val currentIndex = stateHolder.getWalletIndexByWalletId(userWalletId = currentWalletId) ?: return
|
||||
val newIndex = stateHolder.getWalletIndexByWalletId(userWalletId = selectedWalletId) ?: return
|
||||
|
||||
scrollToWallet(prevIndex = currentIndex, newIndex = newIndex) {
|
||||
stateHolder.update { it.copy(selectedWalletIndex = newIndex) }
|
||||
}
|
||||
}
|
||||
|
||||
// We need to update the current wallet quotes if the application was in the background for more than 10 seconds
|
||||
|
|
@ -372,24 +370,26 @@ internal class WalletModel @Inject constructor(
|
|||
coroutineScope = modelScope,
|
||||
)
|
||||
|
||||
fetchIfSingleWallet(action.selectedWallet)
|
||||
fetchWalletContent(userWallet = action.selectedWallet)
|
||||
|
||||
val otherWallets = action.wallets.minus(action.selectedWallet)
|
||||
|
||||
otherWallets.onEach { userWallet ->
|
||||
modelScope.launch { walletContentFetcher(userWalletId = userWallet.walletId) }
|
||||
}
|
||||
|
||||
if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) {
|
||||
withContext(dispatchers.io) { delay(timeMillis = 1_800) }
|
||||
val direction = if (action.selectedWalletIndex == action.wallets.lastIndex) {
|
||||
Direction.RIGHT
|
||||
} else {
|
||||
Direction.LEFT
|
||||
}
|
||||
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.DemonstrateWalletsScrollPreview(
|
||||
direction = if (action.selectedWalletIndex == action.wallets.lastIndex) {
|
||||
Direction.RIGHT
|
||||
} else {
|
||||
Direction.LEFT
|
||||
},
|
||||
),
|
||||
)
|
||||
demonstrateWalletsScrollPreview(direction = direction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) {
|
||||
private suspend fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) {
|
||||
walletScreenContentLoader.cancel(action.prevWalletId)
|
||||
tokenListStore.remove(action.prevWalletId)
|
||||
|
||||
|
|
@ -399,7 +399,7 @@ internal class WalletModel @Inject constructor(
|
|||
coroutineScope = modelScope,
|
||||
)
|
||||
|
||||
fetchIfSingleWallet(userWallet = action.selectedWallet)
|
||||
fetchWalletContent(userWallet = action.selectedWallet)
|
||||
|
||||
stateHolder.update(
|
||||
ReinitializeWalletTransformer(
|
||||
|
|
@ -411,14 +411,14 @@ internal class WalletModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
|
||||
private suspend fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = modelScope,
|
||||
)
|
||||
|
||||
fetchIfSingleWallet(userWallet = action.selectedWallet)
|
||||
fetchWalletContent(userWallet = action.selectedWallet)
|
||||
|
||||
stateHolder.update(
|
||||
AddWalletTransformer(
|
||||
|
|
@ -490,6 +490,16 @@ internal class WalletModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun demonstrateWalletsScrollPreview(direction: Direction) {
|
||||
modelScope.launch(dispatchers.mainImmediate) {
|
||||
delay(timeMillis = 1_800)
|
||||
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.DemonstrateWalletsScrollPreview(direction = direction),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollToWallet(prevIndex: Int, newIndex: Int, onConsume: () -> Unit = {}) {
|
||||
// Should not show scroll animation if WalletScreen isn't in the background.
|
||||
if (screenLifecycleProvider.isBackgroundState.value) {
|
||||
|
|
@ -517,6 +527,20 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchWalletContent(userWallet: UserWallet) {
|
||||
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
/*
|
||||
* Updating the balance of the current wallet is an essential part of InitializationWallets,
|
||||
* so the coroutine is launched in the current context
|
||||
*/
|
||||
supervisorScope {
|
||||
launch { walletContentFetcher(userWalletId = userWallet.walletId) }
|
||||
}
|
||||
} else {
|
||||
fetchIfSingleWallet(userWallet = userWallet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchIfSingleWallet(userWallet: UserWallet) {
|
||||
if (userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isSingleWallet()) {
|
||||
modelScope.launch {
|
||||
|
|
|
|||
|
|
@ -11,19 +11,19 @@ import com.tangem.domain.tokens.FetchCardTokenListUseCase
|
|||
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.FetchTokenListUseCase
|
||||
import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletContentFetcher
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetRefreshStateTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
|
@ -44,13 +44,13 @@ internal class WalletClickIntents @Inject constructor(
|
|||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val selectWalletUseCase: SelectWalletUseCase,
|
||||
private val fetchTokenListUseCase: FetchTokenListUseCase,
|
||||
private val walletContentFetcher: WalletContentFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
private val fetchCardTokenListUseCase: FetchCardTokenListUseCase,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val neverToShowWalletsScrollPreview: NeverToShowWalletsScrollPreview,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val onrampFeatureToggles: OnrampFeatureToggles,
|
||||
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
|
||||
private val onrampStatusFactory: OnrampStatusFactory,
|
||||
) : BaseWalletClickIntents(),
|
||||
|
|
@ -88,6 +88,10 @@ internal class WalletClickIntents @Inject constructor(
|
|||
stateHolder.update { it.copy(selectedWalletIndex = index) }
|
||||
|
||||
maybeUserWallet.onRight {
|
||||
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
launch { walletContentFetcher(userWalletId = it.walletId) }
|
||||
}
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = it,
|
||||
clickIntents = this@WalletClickIntents,
|
||||
|
|
@ -127,36 +131,36 @@ internal class WalletClickIntents @Inject constructor(
|
|||
)
|
||||
|
||||
modelScope.launch {
|
||||
val maybeFetchResult = if (
|
||||
userWallet is UserWallet.Cold &&
|
||||
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
|
||||
) {
|
||||
fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
|
||||
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
walletContentFetcher(userWalletId = userWallet.walletId, forceUpdate = true)
|
||||
} else {
|
||||
fetchTokenListUseCase(userWalletId = userWallet.walletId, mode = RefreshMode.FULL)
|
||||
val isSingleWalletWithToken = userWallet is UserWallet.Cold &&
|
||||
userWallet.cardTypesResolver.isSingleWalletWithToken()
|
||||
|
||||
val maybeFetchResult = if (isSingleWalletWithToken) {
|
||||
fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
|
||||
} else {
|
||||
fetchTokenListUseCase(userWalletId = userWallet.walletId, mode = RefreshMode.FULL)
|
||||
}
|
||||
|
||||
maybeFetchResult.onLeft {
|
||||
stateHolder.update(
|
||||
SetTokenListErrorTransformer(
|
||||
selectedWallet = userWallet,
|
||||
error = it,
|
||||
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
buildList {
|
||||
if (!onrampFeatureToggles.isFeatureEnabled) {
|
||||
async { rampStateManager.fetchBuyServiceData() }.let(::add)
|
||||
}
|
||||
|
||||
async { rampStateManager.fetchSellServiceData() }.let(::add)
|
||||
|
||||
async { fetchHotCryptoUseCase() }.let(::add)
|
||||
}
|
||||
.awaitAll()
|
||||
|
||||
maybeFetchResult.onLeft {
|
||||
stateHolder.update(
|
||||
SetTokenListErrorTransformer(
|
||||
selectedWallet = userWallet,
|
||||
error = it,
|
||||
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
stateHolder.update(
|
||||
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = false),
|
||||
)
|
||||
|
|
@ -172,8 +176,13 @@ internal class WalletClickIntents @Inject constructor(
|
|||
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = showRefreshState),
|
||||
)
|
||||
|
||||
modelScope.launch(dispatchers.main) {
|
||||
fetchCurrencyStatusUseCase(userWallet.walletId, refresh = true)
|
||||
modelScope.launch {
|
||||
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
walletContentFetcher(userWalletId = userWallet.walletId, forceUpdate = true)
|
||||
} else {
|
||||
fetchCurrencyStatusUseCase(userWallet.walletId, refresh = true)
|
||||
}
|
||||
|
||||
onrampStatusFactory.updateOnrmapTransactionStatuses(userWallet)
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -129,7 +128,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val shareManager: ShareManager,
|
||||
private val appRouter: AppRouter,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val onrampFeatureToggles: OnrampFeatureToggles,
|
||||
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
|
||||
|
||||
override fun onSendClick(
|
||||
|
|
@ -345,28 +343,13 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
if (handleUnavailabilityReason(unavailabilityReason)) return
|
||||
|
||||
if (onrampFeatureToggles.isFeatureEnabled) {
|
||||
appRouter.push(
|
||||
AppRoute.Onramp(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
source = OnrampSource.TOKEN_LONG_TAP,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
showErrorIfDemoModeOrElse {
|
||||
modelScope.launch(dispatchers.main) {
|
||||
reduxStateHolder.dispatch(
|
||||
TradeCryptoAction.Buy(
|
||||
userWallet = userWallet,
|
||||
source = OnrampSource.TOKEN_LONG_TAP,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
appRouter.push(
|
||||
AppRoute.Onramp(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
source = OnrampSource.TOKEN_LONG_TAP,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onSwapClick(
|
||||
|
|
@ -482,11 +465,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
override fun onMultiWalletBuyClick(userWalletId: UserWalletId, screenType: String) {
|
||||
onMultiWalletActionClick(
|
||||
statusFlow = if (onrampFeatureToggles.isFeatureEnabled) {
|
||||
rampStateManager.getExpressInitializationStatus(userWalletId)
|
||||
} else {
|
||||
rampStateManager.getBuyInitializationStatus()
|
||||
},
|
||||
statusFlow = rampStateManager.getExpressInitializationStatus(userWalletId),
|
||||
route = AppRoute.BuyCrypto(userWalletId = userWalletId),
|
||||
eventCreator = { MainScreenAnalyticsEvent.ButtonBuy(status = it, screenType = screenType) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ internal interface WalletWarningsClickIntents {
|
|||
fun onSeedPhraseSecondNotificationAccept()
|
||||
|
||||
fun onSeedPhraseSecondNotificationReject()
|
||||
|
||||
fun onFinishWalletActivationClick()
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -369,6 +371,10 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onFinishWalletActivationClick() {
|
||||
// TODO implement wallet activation process
|
||||
}
|
||||
|
||||
private fun getSelectedUserWallet(): UserWallet? {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
return getUserWalletUseCase(userWalletId).getOrElse {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.feature.wallet.deeplink
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionTrigger
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class DefaultWalletDeepLinkActionTrigger @Inject constructor() :
|
||||
WalletDeepLinkActionTrigger,
|
||||
WalletDeepLinkActionListener {
|
||||
|
||||
private val _selectWalletFlow = Channel<UserWalletId>()
|
||||
override val selectWalletFlow: Flow<UserWalletId>
|
||||
get() = _selectWalletFlow.receiveAsFlow()
|
||||
|
||||
override fun selectWallet(userWalletId: UserWalletId) {
|
||||
_selectWalletFlow.trySend(userWalletId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.feature.wallet.deeplink.di
|
||||
|
||||
import com.tangem.feature.wallet.deeplink.DefaultWalletDeepLinkActionTrigger
|
||||
import com.tangem.feature.wallet.deeplink.DefaultWalletDeepLinkHandler
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionTrigger
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -15,4 +18,12 @@ internal interface WalletDeepLinkModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindWalletDeepLinkHandlerFactory(impl: DefaultWalletDeepLinkHandler.Factory): WalletDeepLinkHandler.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindWalletDeepLinkActionTrigger(impl: DefaultWalletDeepLinkActionTrigger): WalletDeepLinkActionTrigger
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindWalletDeepLinkActionListener(impl: DefaultWalletDeepLinkActionTrigger): WalletDeepLinkActionListener
|
||||
}
|
||||
|
|
@ -117,6 +117,7 @@ internal object WalletScreenPreviewData {
|
|||
buttons = persistentListOf(buyButton),
|
||||
warnings = persistentListOf(
|
||||
WalletNotification.Warning.SomeNetworksUnreachable,
|
||||
WalletNotification.FinishWalletActivation { },
|
||||
),
|
||||
bottomSheetConfig = null,
|
||||
tokensListState = textContentTokensState,
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.deeplink
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.deeplink.DeepLink
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.core.deeplink.global.BuyCurrencyDeepLink
|
||||
import com.tangem.core.deeplink.global.SellCurrencyDeepLink
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.isMultiCurrency
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.utils.coroutines.launchOnCancellation
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class WalletDeepLinksHandler @Inject constructor(
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
|
||||
private val onrampFeatureToggles: OnrampFeatureToggles,
|
||||
private val router: AppRouter,
|
||||
) {
|
||||
|
||||
private var deepLinksMap = mutableMapOf<UserWalletId, List<DeepLink>>()
|
||||
|
||||
fun registerForWallet(scope: CoroutineScope, userWallet: UserWallet) {
|
||||
val deepLinks = deepLinksMap.getOrPut(userWallet.walletId) {
|
||||
getDeepLinks(userWallet, scope)
|
||||
}
|
||||
deepLinksRegistry.unregisterByIds(deepLinks.map { it.id })
|
||||
deepLinksRegistry.register(deepLinks = deepLinks)
|
||||
|
||||
// When navigation to another screen scope is Cancelled and deeplinks are hot handled
|
||||
scope.launchOnCancellation {
|
||||
deepLinksRegistry.unregister(deepLinks)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDeepLinks(userWallet: UserWallet, scope: CoroutineScope): List<DeepLink> {
|
||||
val sellCurrencyDeepLink = SellCurrencyDeepLink(
|
||||
onReceive = { data ->
|
||||
scope.launch {
|
||||
onSellCurrencyDeepLink(userWallet, data)
|
||||
}
|
||||
},
|
||||
shouldHandleDelayed = true,
|
||||
)
|
||||
|
||||
return buildList {
|
||||
add(sellCurrencyDeepLink)
|
||||
if (!onrampFeatureToggles.isFeatureEnabled && !userWallet.isMultiCurrency) {
|
||||
add(
|
||||
BuyCurrencyDeepLink(
|
||||
onReceive = {
|
||||
scope.launch { onBuyCurrencyDeepLink(userWallet) }
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onSellCurrencyDeepLink(userWallet: UserWallet, data: SellCurrencyDeepLink.Data) {
|
||||
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet, data.currencyId).getOrNull()
|
||||
|
||||
if (cryptoCurrency == null) {
|
||||
Timber.e("onSellCurrencyDeepLink cryptoCurrency is null")
|
||||
return
|
||||
}
|
||||
|
||||
val route = AppRoute.Send(
|
||||
currency = cryptoCurrency,
|
||||
userWalletId = userWallet.walletId,
|
||||
transactionId = data.transactionId,
|
||||
destinationAddress = data.depositWalletAddress,
|
||||
amount = data.baseCurrencyAmount,
|
||||
tag = data.depositWalletAddressTag,
|
||||
)
|
||||
|
||||
router.push(route)
|
||||
}
|
||||
|
||||
private suspend fun onBuyCurrencyDeepLink(userWallet: UserWallet) {
|
||||
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet.walletId).getOrNull() ?: return
|
||||
analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol))
|
||||
}
|
||||
}
|
||||
|
|
@ -61,6 +61,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
|
|||
is WalletNotification.Warning.NetworksUnreachable,
|
||||
is WalletNotification.UsedOutdatedData,
|
||||
is WalletNotification.UnlockVisaAccess,
|
||||
is WalletNotification.FinishWalletActivation,
|
||||
-> null
|
||||
is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport
|
||||
is WalletNotification.Critical.SeedPhraseSecondNotification -> MainScreen.NoticeSeedPhraseSupportSecond
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
|
||||
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
|
||||
|
||||
addFinishWalletActivationNotification(userWallet, clickIntents)
|
||||
|
||||
addReferralPromoNotification(cardTypesResolver, clickIntents, shouldShowReferralPromo)
|
||||
|
||||
addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents)
|
||||
|
|
@ -259,6 +261,23 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
if (condition) add(element = element)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addFinishWalletActivationNotification(
|
||||
userWallet: UserWallet,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
if (userWallet !is UserWallet.Hot) return
|
||||
|
||||
// TODO [REDACTED_TASK_KEY] set an actual value
|
||||
val shouldShowFinishActivation = false
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.FinishWalletActivation(
|
||||
onFinishClick = clickIntents::onFinishWalletActivationClick,
|
||||
),
|
||||
condition = shouldShowFinishActivation,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_REMAINING_SIGNATURES_COUNT = 10
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,17 @@ package com.tangem.feature.wallet.presentation.wallet.domain
|
|||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
class IsWalletNFTEnabledSyncUseCase(
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val nftFeatureToggles: NFTFeatureToggles,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Boolean = if (nftFeatureToggles.isNFTEnabled) {
|
||||
walletsRepository
|
||||
.nftEnabledStatuses()
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Boolean {
|
||||
val isNFTEnabled = walletsRepository.nftEnabledStatuses()
|
||||
.firstOrNull()
|
||||
?.let { it[userWalletId] }
|
||||
?: false
|
||||
} else {
|
||||
false
|
||||
?.get(userWalletId)
|
||||
|
||||
return isNFTEnabled == true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveInAndJoin
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Wallet content fetcher
|
||||
*
|
||||
* @property walletBalanceFetcher fetcher for wallet balance
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Singleton
|
||||
internal class WalletContentFetcher @Inject constructor(
|
||||
private val walletBalanceFetcher: WalletBalanceFetcher,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
private val fetchingJobMap = ConcurrentHashMap<UserWalletId, JobHolder>()
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, forceUpdate: Boolean = false) = supervisorScope {
|
||||
withContext(dispatchers.default) {
|
||||
// Use mutex to ensure thread safety
|
||||
val jobHolder = mutex.withLock {
|
||||
val savedJobHolder = fetchingJobMap.getOrPut(key = userWalletId, defaultValue = ::JobHolder)
|
||||
|
||||
/*
|
||||
* If this is not a forced update and there is a saved job in the cache
|
||||
* (doesn't matter if it is active or not), then skip the update process.
|
||||
*/
|
||||
if (!forceUpdate && savedJobHolder != null && !savedJobHolder.isEmpty()) {
|
||||
Timber.d("Skip fetching for $userWalletId")
|
||||
|
||||
return@withContext
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is a forced update and there is already a job in the cache that is updating the balance,
|
||||
* then cancel the previous update.
|
||||
*/
|
||||
if (forceUpdate && savedJobHolder?.isActive == true) {
|
||||
Timber.d("Cancel old fetching for $userWalletId")
|
||||
|
||||
savedJobHolder.cancel()
|
||||
}
|
||||
|
||||
JobHolder().also { fetchingJobMap[userWalletId] = it }
|
||||
}
|
||||
|
||||
Timber.d("Start fetching for $userWalletId")
|
||||
|
||||
val maybeResult = launch {
|
||||
walletBalanceFetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
|
||||
.onLeft(Timber::e)
|
||||
}
|
||||
.saveInAndJoin(jobHolder)
|
||||
|
||||
Timber.d("Finish fetching with result $maybeResult for $userWalletId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
|
|
@ -21,11 +19,6 @@ 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.subscribers.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletActionButtonsSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
|
|
@ -45,11 +38,8 @@ internal class MultiWalletContentLoader(
|
|||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val nftFeatureToggles: NFTFeatureToggles,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val routingFeatureToggle: RoutingFeatureToggle,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -64,19 +54,17 @@ internal class MultiWalletContentLoader(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
deepLinksRegistry = deepLinksRegistry,
|
||||
routingFeatureToggle = routingFeatureToggle,
|
||||
).let(::add)
|
||||
if (nftFeatureToggles.isNFTEnabled) {
|
||||
WalletNFTListSubscriber(
|
||||
userWallet = userWallet,
|
||||
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
|
||||
stateHolder = stateHolder,
|
||||
walletsRepository = walletsRepository,
|
||||
clickIntents = clickIntents,
|
||||
currenciesRepository = currenciesRepository,
|
||||
).let(::add)
|
||||
}
|
||||
|
||||
WalletNFTListSubscriber(
|
||||
userWallet = userWallet,
|
||||
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
|
||||
stateHolder = stateHolder,
|
||||
walletsRepository = walletsRepository,
|
||||
clickIntents = clickIntents,
|
||||
currenciesRepository = currenciesRepository,
|
||||
).let(::add)
|
||||
|
||||
MultiWalletWarningsSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
|
|
@ -85,11 +73,13 @@ internal class MultiWalletContentLoader(
|
|||
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
|
||||
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
|
||||
).let(::add)
|
||||
|
||||
MultiWalletActionButtonsSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
getStoryContentUseCase = getStoryContentUseCase,
|
||||
).let(::add)
|
||||
|
||||
WalletDropDownItemsSubscriber(
|
||||
stateHolder = stateHolder,
|
||||
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
|
|
@ -20,7 +18,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -38,11 +35,8 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val nftFeatureToggles: NFTFeatureToggles,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
|
||||
private val routingFeatureToggle: RoutingFeatureToggle,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
) {
|
||||
|
||||
|
|
@ -62,11 +56,8 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
getStoryContentUseCase = getStoryContentUseCase,
|
||||
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
|
||||
deepLinksRegistry = deepLinksRegistry,
|
||||
nftFeatureToggles = nftFeatureToggles,
|
||||
walletsRepository = walletsRepository,
|
||||
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
|
||||
routingFeatureToggle = routingFeatureToggle,
|
||||
currenciesRepository = currenciesRepository,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
|
|
@ -32,8 +30,6 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val routingFeatureToggle: RoutingFeatureToggle,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -47,8 +43,6 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
tokenListStore = tokenListStore,
|
||||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
deepLinksRegistry = deepLinksRegistry,
|
||||
routingFeatureToggle = routingFeatureToggle,
|
||||
).let(::add)
|
||||
MultiWalletWarningsSubscriber(
|
||||
userWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
||||
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender
|
||||
|
|
@ -15,7 +14,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Refactor
|
||||
|
|
@ -33,8 +31,6 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val routingFeatureToggle: RoutingFeatureToggle,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
|
||||
|
|
@ -52,8 +48,6 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
getStoryContentUseCase = getStoryContentUseCase,
|
||||
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
|
||||
deepLinksRegistry = deepLinksRegistry,
|
||||
routingFeatureToggle = routingFeatureToggle,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarCon
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.WalletScreenStateTransformer
|
||||
import com.tangem.utils.extensions.indexOfFirstOrNull
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -68,6 +69,10 @@ internal class WalletStateController @Inject constructor() {
|
|||
return with(value) { wallets[selectedWalletIndex].walletCardState.id }
|
||||
}
|
||||
|
||||
fun getWalletIndexByWalletId(userWalletId: UserWalletId): Int? {
|
||||
return with(value) { wallets.indexOfFirstOrNull { it.walletCardState.id == userWalletId } }
|
||||
}
|
||||
|
||||
fun showBottomSheet(
|
||||
content: TangemBottomSheetConfigContent,
|
||||
userWalletId: UserWalletId = getSelectedWalletId(),
|
||||
|
|
|
|||
|
|
@ -255,6 +255,20 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
),
|
||||
)
|
||||
|
||||
data class FinishWalletActivation(
|
||||
val onFinishClick: () -> Unit,
|
||||
) : WalletNotification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.hw_activation_need_title),
|
||||
subtitle = resourceReference(R.string.hw_activation_need_description),
|
||||
iconResId = R.drawable.img_knight_shield_32,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.hw_activation_need_finish),
|
||||
onClick = onFinishClick,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
data class ReferralPromo(
|
||||
val onCloseClick: () -> Unit,
|
||||
val onClick: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
|||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
|
|
@ -26,12 +25,35 @@ internal class WalletLoadingStateFactory(
|
|||
) {
|
||||
|
||||
fun create(userWallet: UserWallet): WalletState {
|
||||
userWallet.requireColdWallet()
|
||||
return when (userWallet) {
|
||||
is UserWallet.Cold -> {
|
||||
userWallet.createStateByWalletType(
|
||||
multiCurrencyCreator = { createLoadingMultiCurrencyContent(userWallet) },
|
||||
singleCurrencyCreator = { createLoadingSingleCurrencyContent(userWallet) },
|
||||
visaWalletCreator = { createLoadingVisaWalletContent(userWallet) },
|
||||
)
|
||||
}
|
||||
is UserWallet.Hot -> {
|
||||
createLoadingHotWalletContent(userWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return userWallet.createStateByWalletType(
|
||||
multiCurrencyCreator = { createLoadingMultiCurrencyContent(userWallet) },
|
||||
singleCurrencyCreator = { createLoadingSingleCurrencyContent(userWallet) },
|
||||
visaWalletCreator = { createLoadingVisaWalletContent(userWallet) },
|
||||
private fun createLoadingHotWalletContent(userWallet: UserWallet.Hot): WalletState.MultiCurrency.Content {
|
||||
return WalletState.MultiCurrency.Content(
|
||||
pullToRefreshConfig = createPullToRefreshConfig(),
|
||||
walletCardState = WalletCardState.Loading(
|
||||
id = userWallet.walletId,
|
||||
title = userWallet.name,
|
||||
additionalInfo = null, // TODO [REDACTED_TASK_KEY]
|
||||
imageResId = null, // TODO [REDACTED_TASK_KEY]
|
||||
dropDownItems = persistentListOf(),
|
||||
),
|
||||
buttons = createMultiWalletActions(userWallet),
|
||||
warnings = persistentListOf(),
|
||||
bottomSheetConfig = null,
|
||||
tokensListState = WalletTokensListState.ContentState.Loading,
|
||||
nftState = WalletNFTItemUM.Hidden,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -99,15 +121,21 @@ internal class WalletLoadingStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
private fun createMultiWalletActions(userWallet: UserWallet.Cold): PersistentList<WalletManageButton> {
|
||||
val isSingleWalletWithToken = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
|
||||
private fun createMultiWalletActions(userWallet: UserWallet): PersistentList<WalletManageButton> {
|
||||
val isSingleWalletWithToken =
|
||||
userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
|
||||
if (isSingleWalletWithToken) return persistentListOf()
|
||||
|
||||
return persistentListOf(
|
||||
WalletManageButton.Buy(
|
||||
enabled = true,
|
||||
dimContent = false,
|
||||
onClick = { clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId, WALLET_TYPE) },
|
||||
onClick = {
|
||||
clickIntents.onMultiWalletBuyClick(
|
||||
userWalletId = userWallet.walletId,
|
||||
WALLET_TYPE,
|
||||
)
|
||||
},
|
||||
),
|
||||
WalletManageButton.Swap(
|
||||
enabled = true,
|
||||
|
|
@ -124,14 +152,24 @@ internal class WalletLoadingStateFactory(
|
|||
|
||||
private fun createVisaDimmedButtons(): PersistentList<WalletManageButton> {
|
||||
return persistentListOf(
|
||||
WalletManageButton.Receive(enabled = true, dimContent = true, onClick = {}, onLongClick = null),
|
||||
WalletManageButton.Receive(
|
||||
enabled = true,
|
||||
dimContent = true,
|
||||
onClick = {},
|
||||
onLongClick = null,
|
||||
),
|
||||
WalletManageButton.Buy(enabled = true, dimContent = true, onClick = {}),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDimmedButtons(): PersistentList<WalletManageButton> {
|
||||
return persistentListOf(
|
||||
WalletManageButton.Receive(enabled = true, dimContent = true, onClick = {}, onLongClick = null),
|
||||
WalletManageButton.Receive(
|
||||
enabled = true,
|
||||
dimContent = true,
|
||||
onClick = {},
|
||||
onLongClick = null,
|
||||
),
|
||||
WalletManageButton.Send(enabled = true, dimContent = true, onClick = {}),
|
||||
WalletManageButton.Buy(enabled = true, dimContent = true, onClick = {}),
|
||||
WalletManageButton.Sell(enabled = true, dimContent = true, onClick = {}),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.core.deeplink.global.ReferralDeepLink
|
||||
import com.tangem.core.deeplink.global.SellCurrencyDeepLink
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
|
|
@ -39,8 +35,6 @@ internal abstract class BasicTokenListSubscriber(
|
|||
private val walletWithFundsChecker: WalletWithFundsChecker,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val routingFeatureToggle: RoutingFeatureToggle,
|
||||
) : WalletSubscriber() {
|
||||
|
||||
private val sendAnalyticsJobHolder = JobHolder()
|
||||
|
|
@ -48,6 +42,8 @@ internal abstract class BasicTokenListSubscriber(
|
|||
|
||||
protected abstract fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList>
|
||||
|
||||
protected abstract suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>)
|
||||
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<*> {
|
||||
return combine(
|
||||
flow = tokenListFlow(coroutineScope)
|
||||
|
|
@ -114,21 +110,6 @@ internal abstract class BasicTokenListSubscriber(
|
|||
}
|
||||
}
|
||||
|
||||
protected open suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {
|
||||
if (!routingFeatureToggle.isDeepLinkNavigationEnabled) {
|
||||
/* no-op */
|
||||
// Handling sell deeplink requires full content in order to correctly open Send screen
|
||||
if (maybeTokenList.getOrNull(false) != null) {
|
||||
deepLinksRegistry.triggerDelayedDeeplink(deepLinkClass = SellCurrencyDeepLink::class.java)
|
||||
}
|
||||
// Handling referral deeplink requires only selected wallet to be loaded
|
||||
// This is temporary solution, will be removed with complete deeplink navigation overhaul
|
||||
if (maybeTokenList.getOrNull(true) != null) {
|
||||
deepLinksRegistry.triggerDelayedDeeplink(deepLinkClass = ReferralDeepLink::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendTokenListAnalytics(maybeTokenList: Lce<TokenListError, TokenList>) {
|
||||
val displayedState = stateHolder.getWalletStateIfSelected(userWallet.walletId)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
|
|
@ -30,8 +28,6 @@ internal class MultiWalletTokenListSubscriber(
|
|||
walletWithFundsChecker: WalletWithFundsChecker,
|
||||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
deepLinksRegistry: DeepLinksRegistry,
|
||||
routingFeatureToggle: RoutingFeatureToggle,
|
||||
) : BasicTokenListSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
|
|
@ -40,8 +36,6 @@ internal class MultiWalletTokenListSubscriber(
|
|||
walletWithFundsChecker = walletWithFundsChecker,
|
||||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
deepLinksRegistry = deepLinksRegistry,
|
||||
routingFeatureToggle = routingFeatureToggle,
|
||||
) {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
|
|
@ -52,7 +46,6 @@ internal class MultiWalletTokenListSubscriber(
|
|||
|
||||
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {
|
||||
updateSortingIfNeeded(maybeTokenList)
|
||||
super.onTokenListReceived(maybeTokenList)
|
||||
}
|
||||
|
||||
private suspend fun updateSortingIfNeeded(maybeTokenList: Lce<*, TokenList>) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
|
|
@ -25,8 +24,6 @@ internal class SingleWalletWithTokenListSubscriber(
|
|||
walletWithFundsChecker: WalletWithFundsChecker,
|
||||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
deepLinksRegistry: DeepLinksRegistry,
|
||||
routingFeatureToggle: RoutingFeatureToggle,
|
||||
) : BasicTokenListSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
|
|
@ -35,8 +32,6 @@ internal class SingleWalletWithTokenListSubscriber(
|
|||
walletWithFundsChecker = walletWithFundsChecker,
|
||||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
deepLinksRegistry = deepLinksRegistry,
|
||||
routingFeatureToggle = routingFeatureToggle,
|
||||
) {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
|
|
@ -44,4 +39,6 @@ internal class SingleWalletWithTokenListSubscriber(
|
|||
|
||||
return tokenListStore.getOrThrow(userWallet.walletId)
|
||||
}
|
||||
|
||||
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) = Unit
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
|||
import com.tangem.core.ui.res.LocalWindowSize
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TestTags
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import com.tangem.core.ui.utils.lineTo
|
||||
import com.tangem.core.ui.utils.moveTo
|
||||
import com.tangem.core.ui.utils.toPx
|
||||
|
|
@ -178,7 +178,7 @@ private fun WalletContent(
|
|||
} ?: PaddingValues(bottom = TangemTheme.dimens.spacing92 + bottomBarHeight)
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.testTag(TestTags.MAIN_SCREEN),
|
||||
modifier = Modifier.testTag(MainScreenTestTags.SCREEN_CONTAINER),
|
||||
state = listState,
|
||||
contentPadding = contentPadding,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TestTags.MAIN_SCREEN_MORE_BUTTON
|
||||
import com.tangem.core.ui.test.TestTags.MAIN_SCREEN_TOP_BAR
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarConfig
|
||||
|
|
@ -28,7 +27,7 @@ internal fun WalletTopBar(config: WalletTopBarConfig) {
|
|||
Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null)
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = config.onDetailsClick, modifier = Modifier.testTag(MAIN_SCREEN_MORE_BUTTON)) {
|
||||
IconButton(onClick = config.onDetailsClick, modifier = Modifier.testTag(MainScreenTestTags.MORE_BUTTON)) {
|
||||
Icon(painter = painterResource(id = R.drawable.ic_more_vertical_24), contentDescription = null)
|
||||
}
|
||||
},
|
||||
|
|
@ -38,7 +37,7 @@ internal fun WalletTopBar(config: WalletTopBarConfig) {
|
|||
actionIconContentColor = TangemTheme.colors.icon.primary1,
|
||||
),
|
||||
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
|
||||
modifier = Modifier.testTag(MAIN_SCREEN_TOP_BAR),
|
||||
modifier = Modifier.testTag(MainScreenTestTags.TOP_BAR),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue