Updated on 2026-08-14

This commit is contained in:
Tangem 2023-07-07 11:33:27 +08:00
parent b5b086a985
commit 2e07841398
18 changed files with 153 additions and 53 deletions

View file

@ -0,0 +1,20 @@
package com.tangem.tap.common.di.domain.wallets
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
@Module
@InstallIn(ViewModelComponent::class)
object WalletsDomainModule {
@Provides
@ViewModelScoped
fun providesGetWalletsUseCase(walletsStateHolder: WalletsStateHolder): GetWalletsUseCase {
return GetWalletsUseCase(walletsStateHolder = walletsStateHolder)
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.tap.di
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.tap.proxy.AppStateHolder
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface AppStateHolderModule {
@Binds
@Singleton
fun bindsWalletsStateHolder(appStateHolder: AppStateHolder): WalletsStateHolder
}

View file

@ -4,6 +4,7 @@ import com.tangem.TangemSdk
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TangemSdkManager
@ -14,10 +15,12 @@ import org.rekotlin.Store
import javax.inject.Inject
/**
* Holds objects from old modules, that missing in DI graph
* Object sets manually to use in new modules and [AppStateHolder] proxies its to DI
* Holds objects from old modules, that missing in DI graph.
* Object sets manually to use in new modules and [AppStateHolder] proxies its to DI.
*/
class AppStateHolder @Inject constructor() {
class AppStateHolder @Inject constructor() : WalletsStateHolder {
override var userWalletsListManager: UserWalletsListManager? = null
@Deprecated("Use scan response from selected user wallet")
var scanResponse: ScanResponse? = null
@ -27,7 +30,6 @@ class AppStateHolder @Inject constructor() {
var tangemSdkManager: TangemSdkManager? = null
var tangemSdk: TangemSdk? = null
var walletStoresManager: WalletStoresManager? = null
var userWalletsListManager: UserWalletsListManager? = null
var appFiatCurrency: FiatCurrency = FiatCurrency.Default
fun getActualCard(): CardDTO? {

View file

@ -0,0 +1,10 @@
package com.tangem.common
/**
* Provider for lazy initialization
*
* @param action initialization action
*
[REDACTED_AUTHOR]
*/
class Provider<T>(action: () -> T) : () -> T by action

View file

@ -12,6 +12,7 @@ dependencies {
// region Domain modules
implementation(project(":domain:legacy"))
implementation(project(":domain:models"))
implementation(project(":domain:wallets:models"))
// endregion

View file

@ -0,0 +1,6 @@
package com.tangem.domain.wallets.legacy
interface WalletsStateHolder {
val userWalletsListManager: UserWalletsListManager?
}

View file

@ -0,0 +1,20 @@
package com.tangem.domain.wallets.usecase
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.models.UserWallet
import kotlinx.coroutines.flow.Flow
/**
* Use case for getting list of user wallets
*
* @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager'
*
[REDACTED_AUTHOR]
*/
class GetWalletsUseCase(private val walletsStateHolder: WalletsStateHolder) {
@Throws(IllegalArgumentException::class)
operator fun invoke(): Flow<List<UserWallet>> {
return requireNotNull(walletsStateHolder.userWalletsListManager).userWallets
}
}

View file

@ -40,5 +40,9 @@ dependencies {
implementation(project(":features:wallet:api"))
/** Domain modules */
implementation(project(":common"))
implementation(project(":domain:legacy"))
implementation(project(":domain:models"))
implementation(project(":domain:wallets"))
implementation(project(":domain:wallets:models"))
}

View file

@ -47,7 +47,7 @@ internal class WalletFragment : Fragment() {
}
isTransitionGroup = true
_walletRouter.Initialize()
_walletRouter.Initialize(fragmentManager = requireActivity().supportFragmentManager)
}
}
}

View file

@ -111,7 +111,7 @@ internal object WalletPreviewData {
name = "Polygon",
)
val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString())
val loadingTokenItemState = TokenItemState.Loading
private const val networksSize = 10
private const val tokensSize = 3

View file

@ -7,20 +7,12 @@ import androidx.compose.runtime.Immutable
@Immutable
internal sealed interface TokenItemState {
/** Token id */
val id: String
/**
* Loading token state
*
* @property id token id
*/
data class Loading(override val id: String) : TokenItemState
/** Loading token state */
object Loading : TokenItemState
/**
* Content token state
*
* @property id token id
* @property tokenIconUrl token icon url
* @property tokenIconResId token icon resource id
* @property networkIconResId network icon resource id, may be null if it is a coin
@ -30,7 +22,7 @@ internal sealed interface TokenItemState {
* @property tokenOptions state for token options
*/
data class Content(
override val id: String,
val id: String,
val tokenIconUrl: String?,
@DrawableRes val tokenIconResId: Int,
@DrawableRes val networkIconResId: Int?,
@ -43,14 +35,13 @@ internal sealed interface TokenItemState {
/**
* Draggable token state
*
* @property id token id
* @property tokenIconUrl token icon url
* @property tokenIconResId token icon resource id
* @property networkIconResId network icon resource id, may be null if it is a coin
* @property name token name
*/
data class Draggable(
override val id: String,
val id: String,
val tokenIconUrl: String?,
@DrawableRes val tokenIconResId: Int,
@DrawableRes val networkIconResId: Int?,
@ -68,7 +59,7 @@ internal sealed interface TokenItemState {
* @property name token name
*/
data class Unreachable(
override val id: String,
val id: String,
val tokenIconUrl: String?,
@DrawableRes val tokenIconResId: Int,
@DrawableRes val networkIconResId: Int?,

View file

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
@ -22,11 +23,14 @@ import kotlin.properties.Delegates
internal class DefaultWalletRouter : InnerWalletRouter {
private var navController: NavHostController by Delegates.notNull()
private var fragmentManager: FragmentManager by Delegates.notNull()
override fun getEntryFragment(): Fragment = WalletFragment.create()
@Composable
override fun Initialize() {
override fun Initialize(fragmentManager: FragmentManager) {
this.fragmentManager = fragmentManager
TangemTheme {
NavHost(
navController = rememberNavController().apply { navController = this },
@ -53,10 +57,24 @@ internal class DefaultWalletRouter : InnerWalletRouter {
}
override fun popBackStack() {
navController.popBackStack()
/*
* It's hack that avoid issue with closing the wallet screen.
* We are using NavGraph only inside feature so first backstack's element is entry of NavGraph and
* next element is wallet screen entry.
* If backstack contains only NavGraph entry and wallet screen entry then we close the wallet fragment.
*/
if (navController.backQueue.size == BACKSTACK_ENTRY_COUNT_TO_CLOSE_WALLET_SCREEN) {
fragmentManager.popBackStack()
} else {
navController.popBackStack()
}
}
override fun openOrganizeTokensScreen() {
navController.navigate(WalletScreens.ORGANIZE_TOKENS.name)
}
private companion object {
const val BACKSTACK_ENTRY_COUNT_TO_CLOSE_WALLET_SCREEN = 2
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.router
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.fragment.app.FragmentManager
import com.tangem.features.wallet.navigation.WalletRouter
/**
@ -15,10 +16,14 @@ import com.tangem.features.wallet.navigation.WalletRouter
@Stable
internal interface InnerWalletRouter : WalletRouter {
/** Initialize router */
/**
* Initialize router
*
* @param fragmentManager fragment manager
*/
@Suppress("TopLevelComposableFunctions")
@Composable
fun Initialize()
fun Initialize(fragmentManager: FragmentManager)
/** Pop back stack */
fun popBackStack()

View file

@ -18,7 +18,7 @@ internal sealed interface WalletCardState {
/** Wallet image resource id */
@get:DrawableRes
val imageResId: Int
val imageResId: Int?
/** Lambda be invoked when card is clicked */
val onClick: (() -> Unit)?
@ -37,7 +37,7 @@ internal sealed interface WalletCardState {
override val id: String,
override val title: String,
override val additionalInfo: String,
override val imageResId: Int,
override val imageResId: Int?,
override val onClick: (() -> Unit)? = null,
val balance: String,
) : WalletCardState
@ -55,8 +55,8 @@ internal sealed interface WalletCardState {
override val id: String,
override val title: String,
override val additionalInfo: String,
override val imageResId: Int,
override val onClick: (() -> Unit)?,
override val imageResId: Int?,
override val onClick: (() -> Unit)? = null,
) : WalletCardState
/**
@ -72,7 +72,7 @@ internal sealed interface WalletCardState {
override val id: String,
override val title: String,
override val additionalInfo: String,
override val imageResId: Int,
override val imageResId: Int?,
override val onClick: (() -> Unit)?,
) : WalletCardState
@ -89,7 +89,7 @@ internal sealed interface WalletCardState {
override val id: String,
override val title: String,
override val additionalInfo: String,
override val imageResId: Int,
override val imageResId: Int?,
override val onClick: (() -> Unit)?,
) : WalletCardState
}

View file

@ -52,4 +52,6 @@ internal sealed interface WalletContentItemState {
*/
data class Transaction(val state: TransactionState) : SingleCurrencyItem
}
object Loading : WalletContentItemState
}

View file

@ -25,6 +25,7 @@ import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.component.NetworkGroupItem
import com.tangem.feature.wallet.presentation.common.component.TokenItem
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.ui.components.WalletTopBar
@ -67,7 +68,6 @@ internal fun WalletScreen(state: WalletStateHolder) {
WalletsList(
config = state.walletsListConfig,
lazyListState = walletsListState,
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing14),
)
}
@ -75,7 +75,7 @@ internal fun WalletScreen(state: WalletStateHolder) {
item {
WalletManageButtons(
buttons = state.buttons,
modifier = changeableItemModifier.padding(bottom = TangemTheme.dimens.spacing14),
modifier = changeableItemModifier.padding(top = TangemTheme.dimens.spacing14),
)
}
}
@ -86,8 +86,8 @@ internal fun WalletScreen(state: WalletStateHolder) {
Notification(
state = item.state,
modifier = changeableItemModifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.padding(bottom = TangemTheme.dimens.spacing14),
.padding(top = TangemTheme.dimens.spacing14)
.padding(horizontal = TangemTheme.dimens.spacing16),
)
},
)
@ -96,7 +96,9 @@ internal fun WalletScreen(state: WalletStateHolder) {
item {
WalletMarketplaceBlock(
state = state.marketplaceBlockState,
modifier = changeableItemModifier.padding(horizontal = TangemTheme.dimens.spacing16),
modifier = changeableItemModifier
.padding(top = TangemTheme.dimens.spacing14)
.padding(horizontal = TangemTheme.dimens.spacing16),
)
}
}
@ -106,10 +108,11 @@ internal fun WalletScreen(state: WalletStateHolder) {
key = { index, item ->
when (item) {
is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> item.networkName
is WalletContentItemState.MultiCurrencyItem.Token -> item.state.id
is WalletContentItemState.MultiCurrencyItem.Token -> index
is WalletContentItemState.SingleCurrencyItem.Title -> index
is WalletContentItemState.SingleCurrencyItem.GroupTitle -> item.title
is WalletContentItemState.SingleCurrencyItem.Transaction -> index
is WalletContentItemState.Loading -> index
}
},
itemContent = { index, item ->
@ -155,6 +158,9 @@ private fun ContentItem(item: WalletContentItemState, modifier: Modifier = Modif
is WalletContentItemState.SingleCurrencyItem.Transaction -> {
Transaction(state = item.state, modifier = modifier)
}
WalletContentItemState.Loading -> {
TokenItem(state = TokenItemState.Loading, modifier = modifier)
}
}
}

View file

@ -62,16 +62,18 @@ internal fun WalletCard(state: WalletCardState, modifier: Modifier = Modifier) {
}
val imageWidth = TangemTheme.dimens.size120
WalletImage(
id = state.imageResId,
modifier = Modifier.constrainAs(imageItem) {
centerVerticallyTo(parent)
top.linkTo(parent.top)
end.linkTo(parent.end)
height = Dimension.fillToConstraints
width = Dimension.value(imageWidth)
},
)
state.imageResId?.let {
WalletImage(
id = it,
modifier = Modifier.constrainAs(imageItem) {
centerVerticallyTo(parent)
top.linkTo(parent.top)
end.linkTo(parent.end)
height = Dimension.fillToConstraints
width = Dimension.value(imageWidth)
},
)
}
}
}
}

View file

@ -20,13 +20,8 @@ internal class ScrollOffsetCollector(
private val LazyListItemInfo.halfItemSize get() = size.div(other = 2)
override suspend fun emit(value: List<LazyListItemInfo>) {
val firstItem = requireNotNull(value.firstOrNull()) {
"At least 1 item should be visible in list"
}
val lastItem = requireNotNull(value.lastOrNull()) {
"At least 1 item should be visible in list"
}
val firstItem = value.firstOrNull() ?: return
val lastItem = value.lastOrNull() ?: return
if (abs(firstItem.offset) > firstItem.halfItemSize) {
callback(firstItem.index + 1)