Updated on 2026-08-14
This commit is contained in:
commit
6ad667d49d
77 changed files with 880 additions and 343 deletions
|
|
@ -20,6 +20,12 @@ internal object WalletsDomainModule {
|
|||
return GetWalletsUseCase(walletsStateHolder = walletsStateHolder)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun providesGetUserWalletUseCase(walletsStateHolder: WalletsStateHolder): GetUserWalletUseCase {
|
||||
return GetUserWalletUseCase(walletsStateHolder = walletsStateHolder)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
fun providesGetSelectedWalletUseCase(walletsStateHolder: WalletsStateHolder): GetSelectedWalletUseCase {
|
||||
|
|
|
|||
|
|
@ -91,10 +91,10 @@ class TapWalletManager(
|
|||
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
|
||||
store.dispatchWalletAction(action = WalletAction.Warnings.CheckIfNeeded)
|
||||
}
|
||||
setupWalletConnectV2(userWallet)
|
||||
|
||||
val walletFeatureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
|
||||
if (!walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
setupWalletConnectV2(userWallet)
|
||||
loadData(userWallet = userWallet, refresh = refresh)
|
||||
}
|
||||
}
|
||||
|
|
@ -161,21 +161,31 @@ class TapWalletManager(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getAccountsForWc(wcInteractor: WalletConnectInteractor): List<Account> {
|
||||
return store.state.walletState.walletManagers
|
||||
.mapNotNull {
|
||||
val wallet = it.wallet
|
||||
val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull(
|
||||
wallet.blockchain.toNetworkId(),
|
||||
private suspend fun getAccountsForWc(wcInteractor: WalletConnectInteractor): List<Account> {
|
||||
val walletManagerToggles = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletFeatureToggles)
|
||||
val walletManagers = if (walletManagerToggles.isRedesignedScreenEnabled) {
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList()
|
||||
walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
|
||||
} else {
|
||||
store.state.walletState.walletManagers
|
||||
}
|
||||
|
||||
return walletManagers.mapNotNull {
|
||||
val wallet = it.wallet
|
||||
val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull(
|
||||
wallet.blockchain.toNetworkId(),
|
||||
)
|
||||
chainId?.let {
|
||||
Account(
|
||||
chainId,
|
||||
wallet.address,
|
||||
wallet.publicKey.derivationPath?.rawPath,
|
||||
)
|
||||
chainId?.let {
|
||||
Account(
|
||||
chainId,
|
||||
wallet.address,
|
||||
wallet.publicKey.derivationPath?.rawPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateConfigManager(data: ScanResponse) {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ private fun TextField(model: AddCustomTokenInputField, isError: Boolean) {
|
|||
label = {
|
||||
Text(
|
||||
text = model.label.resolveReference(),
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTextFieldsDefault.defaultTextFieldColors.labelColor(
|
||||
enabled = isEnabled,
|
||||
error = isError,
|
||||
|
|
@ -178,7 +178,7 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
|
|||
text = subtitle,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,10 @@ import com.tangem.domain.common.extensions.*
|
|||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
|
|
@ -59,14 +62,16 @@ import javax.inject.Inject
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LargeClass")
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
@HiltViewModel
|
||||
internal class AddCustomTokenViewModel @Inject constructor(
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
featureRouter: CustomTokenRouter,
|
||||
getCurrenciesUseCase: GetCryptoCurrenciesUseCase,
|
||||
private val featureInteractor: CustomTokenInteractor,
|
||||
private val dispatchers: AppCoroutineDispatcherProvider,
|
||||
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
||||
private val analyticsSender = AddCustomTokenAnalyticsSender(analyticsEventHandler)
|
||||
|
|
@ -74,12 +79,30 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
private val testActionsHandler = TestActionsHandler()
|
||||
private val formStateBuilder = FormStateBuilder()
|
||||
|
||||
private var currentCryptoCurrencies: List<CryptoCurrency> = emptyList()
|
||||
|
||||
/** Screen state */
|
||||
var uiState by mutableStateOf(getInitialUiState())
|
||||
private set
|
||||
|
||||
private var foundToken: FoundToken? = null
|
||||
|
||||
init {
|
||||
if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
currentCryptoCurrencies = getSelectedWalletUseCase().fold(
|
||||
ifLeft = { emptyList() },
|
||||
ifRight = { selectedWallet ->
|
||||
getCurrenciesUseCase(selectedWallet.walletId).fold(
|
||||
ifLeft = { emptyList() },
|
||||
ifRight = { it },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
analyticsSender.sendWhenScreenOpened()
|
||||
}
|
||||
|
|
@ -564,6 +587,33 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun isTokenAlreadyAdded(): Boolean {
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
isTokenAlreadyAddedNew()
|
||||
} else {
|
||||
isTokenAlreadyAddedOld()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTokenAlreadyAddedNew(): Boolean {
|
||||
return currentCryptoCurrencies
|
||||
.filterIsInstance<CryptoCurrency.Token>()
|
||||
.any { token ->
|
||||
val contractAddress = uiState.form.contractAddressInputField.value
|
||||
val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
|
||||
val networkId = Blockchain.fromNetworkId(networkSelectorValue.toNetworkId())?.id
|
||||
|
||||
val savedTokenId = if (token.isCustom) null else token.id.value
|
||||
|
||||
val sameId = foundToken?.id == savedTokenId
|
||||
val sameAddress = contractAddress == token.contractAddress
|
||||
val sameBlockchain = networkId == token.network.id.value
|
||||
val isSameDerivationPath = getDerivationPath()?.rawPath == token.network.derivationPath.value
|
||||
|
||||
sameId && sameAddress && sameBlockchain && isSameDerivationPath
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTokenAlreadyAddedOld(): Boolean {
|
||||
return store.state.walletState.walletsStores
|
||||
.map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
|
||||
.flatten()
|
||||
|
|
@ -581,6 +631,23 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun isBlockchainAlreadyAdded(): Boolean {
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
isBlockchainAlreadyAddedNew()
|
||||
} else {
|
||||
isBlockchainAlreadyAddedOld()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBlockchainAlreadyAddedNew(): Boolean {
|
||||
return currentCryptoCurrencies
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.any { coin ->
|
||||
coin.network.id.value == uiState.form.networkSelectorField.selectedItem.blockchain.id &&
|
||||
coin.network.derivationPath.value == getDerivationPath()?.rawPath
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBlockchainAlreadyAddedOld(): Boolean {
|
||||
return store.state.walletState.walletsStores
|
||||
.map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
|
||||
.flatten()
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import com.tangem.domain.common.extensions.withMainContext
|
|||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.walletconnect.WalletConnectActions
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -32,6 +34,7 @@ import com.tangem.tap.features.wallet.redux.WalletState
|
|||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -59,6 +62,28 @@ class WalletConnectMiddleware {
|
|||
if (DemoHelper.tryHandle(state, action)) return
|
||||
|
||||
when (action) {
|
||||
is WalletConnectActions.New.Initialize -> {
|
||||
val userWallet = action.userWallet
|
||||
val cardId = if (userWallet.scanResponse.card.backupStatus?.isActive != true) {
|
||||
userWallet.cardId
|
||||
} else { // if wallet has backup, any card from wallet can be used to sign
|
||||
null
|
||||
}
|
||||
scope.launch {
|
||||
val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch
|
||||
wcInteractor.startListening(
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
cardId = cardId,
|
||||
)
|
||||
}
|
||||
}
|
||||
is WalletConnectActions.New.SetupUserChains -> {
|
||||
scope.launch {
|
||||
val userWallet = action.userWallet
|
||||
val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch
|
||||
wcInteractor.setUserChains(getAccountsForWc(wcInteractor, userWallet))
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.ResetState -> walletConnectManager = WalletConnectManager()
|
||||
is WalletConnectAction.RestoreSessions -> {
|
||||
walletConnectManager.restoreSessions(action.scanResponse)
|
||||
|
|
@ -246,47 +271,50 @@ class WalletConnectMiddleware {
|
|||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork()))
|
||||
return
|
||||
}
|
||||
val walletManager = getWalletManager(
|
||||
wallet = action.session.wallet,
|
||||
blockchain = blockchain,
|
||||
walletState = store.state.walletState,
|
||||
).guard {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.AddNetwork(blockchain.fullName),
|
||||
),
|
||||
scope.launch {
|
||||
val walletManager = getWalletManager(
|
||||
wallet = action.session.wallet,
|
||||
blockchain = blockchain,
|
||||
walletState = store.state.walletState,
|
||||
).guard {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.AddNetwork(blockchain.fullName),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
val updatedWallet = action.session.wallet.copy(
|
||||
walletPublicKey = walletManager.wallet.publicKey.seedKey,
|
||||
derivedPublicKey = walletManager.wallet.publicKey.derivedKey,
|
||||
derivationPath = walletManager.wallet.publicKey.derivationPath,
|
||||
blockchain = action.blockchain,
|
||||
)
|
||||
return
|
||||
val updatedSession = action.session.copy(wallet = updatedWallet)
|
||||
store.dispatchOnMain(WalletConnectAction.UpdateBlockchain(updatedSession))
|
||||
}
|
||||
val updatedWallet = action.session.wallet.copy(
|
||||
walletPublicKey = walletManager.wallet.publicKey.seedKey,
|
||||
derivedPublicKey = walletManager.wallet.publicKey.derivedKey,
|
||||
derivationPath = walletManager.wallet.publicKey.derivationPath,
|
||||
blockchain = action.blockchain,
|
||||
)
|
||||
val updatedSession = action.session.copy(wallet = updatedWallet)
|
||||
store.dispatchOnMain(WalletConnectAction.UpdateBlockchain(updatedSession))
|
||||
}
|
||||
is WalletConnectAction.UpdateBlockchain -> {
|
||||
walletConnectManager.updateBlockchain(action.updatedSession)
|
||||
}
|
||||
is WalletConnectAction.ApproveProposal -> {
|
||||
val accounts = store.state.walletState.walletManagers
|
||||
.mapNotNull {
|
||||
val wallet = it.wallet
|
||||
val chainId = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull(
|
||||
wallet.blockchain.toNetworkId(),
|
||||
)
|
||||
chainId?.let {
|
||||
Account(
|
||||
chainId,
|
||||
wallet.address,
|
||||
wallet.publicKey.derivationPath?.rawPath,
|
||||
scope.launch {
|
||||
val accounts = getWalletManagers()
|
||||
.mapNotNull {
|
||||
val wallet = it.wallet
|
||||
val chainId = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull(
|
||||
wallet.blockchain.toNetworkId(),
|
||||
)
|
||||
chainId?.let {
|
||||
Account(
|
||||
chainId,
|
||||
wallet.address,
|
||||
wallet.publicKey.derivationPath?.rawPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walletConnectInteractor.approveSessionProposal(accounts)
|
||||
walletConnectInteractor.approveSessionProposal(accounts)
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.RejectProposal -> {
|
||||
walletConnectInteractor.rejectSessionProposal()
|
||||
|
|
@ -345,6 +373,19 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getWalletManagers(): List<WalletManager> {
|
||||
val walletManagerToggles = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletFeatureToggles)
|
||||
return if (walletManagerToggles.isRedesignedScreenEnabled) {
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList()
|
||||
walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
|
||||
} else {
|
||||
store.state.walletState.walletManagers
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanCard(scanResponse: ScanResponse, session: WalletConnectSession, chainId: Int?) {
|
||||
val blockchain = WalletConnectNetworkUtils.parseBlockchain(
|
||||
chainId = chainId,
|
||||
|
|
@ -430,7 +471,7 @@ class WalletConnectMiddleware {
|
|||
)
|
||||
}
|
||||
|
||||
private fun getWalletManager(
|
||||
private suspend fun getWalletManager(
|
||||
wallet: WalletForSession,
|
||||
blockchain: Blockchain,
|
||||
walletState: WalletState,
|
||||
|
|
@ -440,18 +481,50 @@ class WalletConnectMiddleware {
|
|||
} else {
|
||||
blockchain
|
||||
}
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return null
|
||||
val derivation = blockchainToMake.derivationPath(
|
||||
style = store.state.globalState.scanResponse?.derivationStyleProvider?.getDerivationStyle(),
|
||||
style = userWallet.scanResponse.derivationStyleProvider.getDerivationStyle(),
|
||||
)?.rawPath
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = derivation,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
return walletState.getWalletManager(blockchainNetwork)
|
||||
val walletFeatureToggles = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletFeatureToggles)
|
||||
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
walletManagerFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = derivation,
|
||||
)
|
||||
} else {
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = derivation,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
walletState.getWalletManager(blockchainNetwork)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWalletConnectUri(uri: String): Boolean {
|
||||
return WalletConnectManager.isCorrectWcUri(uri) || walletConnectInteractor.isWalletConnectUri(uri)
|
||||
}
|
||||
|
||||
private suspend fun getAccountsForWc(wcInteractor: WalletConnectInteractor, userWallet: UserWallet): List<Account> {
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
return walletManagerFacade.getStoredWalletManagers(userWallet.walletId).mapNotNull {
|
||||
val wallet = it.wallet
|
||||
val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull(
|
||||
wallet.blockchain.toNetworkId(),
|
||||
)
|
||||
chainId?.let {
|
||||
Account(
|
||||
chainId,
|
||||
wallet.address,
|
||||
wallet.publicKey.derivationPath?.rawPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -229,7 +229,7 @@ private fun TangemAppVersion(appNameRes: Int, version: String, modifier: Modifie
|
|||
Text(
|
||||
modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
text = "${stringResource(id = appNameRes)} $version",
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.tangem.core.analytics.Analytics
|
|||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.navigation.OnboardingRouter
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseRouter
|
||||
|
|
@ -62,6 +63,8 @@ class OnboardingWalletFragment :
|
|||
|
||||
internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer }
|
||||
|
||||
private val canSkipBackup by lazy { arguments?.getBoolean(OnboardingRouter.CAN_SKIP_BACKUP) ?: true }
|
||||
|
||||
private val seedPhraseStateHandler: OnboardingSeedPhraseStateHandler = OnboardingSeedPhraseStateHandler()
|
||||
private val seedPhraseViewModel by viewModels<SeedPhraseViewModel>()
|
||||
|
||||
|
|
@ -255,7 +258,7 @@ class OnboardingWalletFragment :
|
|||
|
||||
btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
|
||||
btnWalletAlternativeAction.setOnClickListener { store.dispatch(BackupAction.SkipBackup) }
|
||||
btnWalletAlternativeAction.show(state.canSkipBackup)
|
||||
btnWalletAlternativeAction.show(state.canSkipBackup && canSkipBackup)
|
||||
}
|
||||
animator.showBackupIntro(state)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ private fun Footer(showProgress: Boolean, onSaveWalletClick: () -> Unit) {
|
|||
Text(
|
||||
modifier = Modifier.fillMaxWidth(fraction = .7f),
|
||||
text = stringResource(R.string.save_user_wallet_agreement_notice),
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,13 @@ class MultiWalletMiddleware {
|
|||
when (action) {
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
if (action.currency != null) {
|
||||
val userWalletId = userWalletsListManager.selectedUserWalletSync?.walletId
|
||||
|
||||
val bundle = bundleOf(
|
||||
TokenDetailsRouter.USER_WALLET_ID_KEY to userWalletId?.stringValue,
|
||||
TokenDetailsRouter.CRYPTO_CURRENCY_KEY to cryptoCurrencyConverter.convert(action.currency),
|
||||
)
|
||||
|
||||
store.dispatch(NavigationAction.NavigateTo(screen = AppScreen.WalletDetails, bundle = bundle))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ private fun TotalBalanceCardContent(state: TotalBalanceCardState, modifier: Modi
|
|||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.main_processing_full_amount),
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.attention,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ private fun RowScope.WalletInfo(wallet: UserWalletItem, isSelected: Boolean) {
|
|||
},
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -255,7 +255,7 @@ private fun LoadedTokensInfo(
|
|||
count = tokensCount,
|
||||
tokensCount,
|
||||
),
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.End,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dependencies {
|
|||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(deps.tangem.blockchain)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.txhistory.DefaultTxHistoryItemsStore
|
||||
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object TxHistoryItemsStoreModule {
|
||||
|
||||
@Provides
|
||||
fun provideTxHistoryItemsStore(): TxHistoryItemsStore {
|
||||
return DefaultTxHistoryItemsStore(
|
||||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.datasource.local.txhistory
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
|
||||
internal class DefaultTxHistoryItemsStore(
|
||||
dataStore: StringKeyDataStore<Set<PaginationWrapper<TxHistoryItem>>>,
|
||||
) : TxHistoryItemsStore,
|
||||
StringKeyDataStoreDecorator<TxHistoryItemsStore.Key, Set<PaginationWrapper<TxHistoryItem>>>(dataStore) {
|
||||
|
||||
override fun provideStringKey(key: TxHistoryItemsStore.Key): String = key.toString()
|
||||
|
||||
override suspend fun getNextPageSyncOrNull(key: TxHistoryItemsStore.Key): Int? {
|
||||
val storedValue = getSyncOrNull(key) ?: return null
|
||||
val lastWrappedItems = storedValue.maxBy(PaginationWrapper<*>::page)
|
||||
val lastPage = lastWrappedItems.page
|
||||
|
||||
return if (lastPage <= lastWrappedItems.totalPages) {
|
||||
lastPage
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(key: TxHistoryItemsStore.Key, page: Int): PaginationWrapper<TxHistoryItem>? {
|
||||
val storedValue = getSyncOrNull(key)
|
||||
|
||||
return storedValue?.firstOrNull { it.page == page }
|
||||
}
|
||||
|
||||
override suspend fun store(key: TxHistoryItemsStore.Key, value: PaginationWrapper<TxHistoryItem>) {
|
||||
val oldValue = getSyncOrNull(key).orEmpty()
|
||||
val newValue = oldValue.addOrReplace(value) {
|
||||
it.page == value.page
|
||||
}
|
||||
|
||||
store(key, newValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.datasource.local.txhistory
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface TxHistoryItemsStore {
|
||||
|
||||
suspend fun getNextPageSyncOrNull(key: Key): Int?
|
||||
|
||||
suspend fun getSyncOrNull(key: Key, page: Int): PaginationWrapper<TxHistoryItem>?
|
||||
|
||||
suspend fun remove(key: Key)
|
||||
|
||||
suspend fun store(key: Key, value: PaginationWrapper<TxHistoryItem>)
|
||||
|
||||
data class Key(
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
)
|
||||
}
|
||||
|
|
@ -33,6 +33,10 @@ internal class DefaultWalletManagersStore(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getAllSync(userWalletId: UserWalletId): List<WalletManager> {
|
||||
return getSyncOrNull(userWalletId) ?: emptyList()
|
||||
}
|
||||
|
||||
override suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager) {
|
||||
val walletManagers = getSyncOrNull(userWalletId)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ interface WalletManagersStore {
|
|||
derivationPath: String?,
|
||||
): WalletManager?
|
||||
|
||||
suspend fun getAllSync(userWalletId: UserWalletId): List<WalletManager>
|
||||
|
||||
suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager)
|
||||
|
||||
suspend fun clear()
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=213%3A218&t=TmfD6UBHPg9uYfev-4)
|
||||
|
|
@ -103,7 +102,7 @@ private fun TangemTextField(
|
|||
if (!label.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = label,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = colors.labelColor(
|
||||
enabled = enabled,
|
||||
error = isError,
|
||||
|
|
@ -161,7 +160,7 @@ private fun TangemTextField(
|
|||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = caption,
|
||||
style = TangemTypography.body1,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = colors.captionColor(enabled = enabled, isError = isError).value,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ private fun SubtitleView(subtitle: String, icon: Painter?) {
|
|||
text = subtitle,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ private fun TokenReceiveBottomSheetContent(content: TokenReceiveBottomSheetConfi
|
|||
text = stringResource(R.string.receive_bottom_sheet_warning_message_full, content.name),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
|
|
@ -100,15 +100,18 @@ private fun TokenReceiveBottomSheetContent(content: TokenReceiveBottomSheetConfi
|
|||
@Composable
|
||||
private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChange: (AddressModel) -> Unit) {
|
||||
val qrCodes = rememberQrPainters(content.addresses.map(AddressModel::value))
|
||||
val pagerState = rememberPagerState()
|
||||
val pageCount = content.addresses.count()
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = 0,
|
||||
initialPageOffsetFraction = 0f,
|
||||
) {
|
||||
content.addresses.count()
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = pagerState.currentPage) {
|
||||
onAddressChange.invoke(content.addresses[pagerState.currentPage])
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
pageCount = pageCount,
|
||||
state = pagerState,
|
||||
) { currentPage ->
|
||||
Column(
|
||||
|
|
@ -142,7 +145,7 @@ private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChang
|
|||
}
|
||||
}
|
||||
|
||||
if (pageCount > 1) {
|
||||
if (pagerState.pageCount > 1) {
|
||||
val indicatorState = rememberLazyListState()
|
||||
val selectedColor = TangemTheme.colors.icon.primary1
|
||||
val unselectedColor = TangemTheme.colors.icon.informative
|
||||
|
|
@ -153,7 +156,7 @@ private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChang
|
|||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
repeat(pageCount) { iteration ->
|
||||
repeat(pagerState.pageCount) { iteration ->
|
||||
item(key = iteration) {
|
||||
val color = if (pagerState.currentPage == iteration) {
|
||||
selectedColor
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ fun HorizontalActionChips(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
// do not use key cause when change items order, list is scrolled
|
||||
items(
|
||||
items = buttons,
|
||||
key = { config -> config.text.hashCode() },
|
||||
itemContent = { ActionButton(config = it) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) {
|
|||
Text(
|
||||
text = subtitle.resolveReference(),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
modifier = modifier,
|
||||
textAlign = TextAlign.Start,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
is TransactionState.Loading -> {
|
||||
|
|
@ -304,7 +304,7 @@ private fun Timestamp(state: TransactionState, modifier: Modifier = Modifier) {
|
|||
modifier = modifier,
|
||||
textAlign = TextAlign.End,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
is TransactionState.Loading -> {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.core.ui.res
|
|||
import androidx.compose.material.Colors
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ProvideTextStyle
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
|
|
@ -17,7 +16,7 @@ internal const val IS_SYSTEM_IN_DARK_THEME: Boolean = false
|
|||
@Composable
|
||||
fun TangemTheme(
|
||||
isDark: Boolean = false,
|
||||
typography: Typography = TangemTheme.typography,
|
||||
typography: TangemTypography = TangemTheme.typography,
|
||||
dimens: TangemDimens = TangemTheme.dimens,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
|
|
@ -26,10 +25,8 @@ fun TangemTheme(
|
|||
.also { it.update(themeColors) }
|
||||
|
||||
val shapes = remember { TangemShapes(dimens) }
|
||||
|
||||
MaterialTheme(
|
||||
colors = materialThemeColors(colors = themeColors, isDark = isDark),
|
||||
typography = typography,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalTangemColors provides rememberedColors,
|
||||
|
|
@ -52,7 +49,7 @@ object TangemTheme {
|
|||
@ReadOnlyComposable
|
||||
get() = LocalTangemColors.current
|
||||
|
||||
val typography: Typography
|
||||
val typography: TangemTypography
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalTangemTypography.current
|
||||
|
|
@ -197,7 +194,7 @@ private val LocalTangemColors = staticCompositionLocalOf<TangemColors> {
|
|||
}
|
||||
|
||||
private val LocalTangemTypography = staticCompositionLocalOf {
|
||||
TangemTypography
|
||||
TangemTypography()
|
||||
}
|
||||
|
||||
private val LocalTangemDimens = staticCompositionLocalOf {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.core.ui.res
|
||||
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
|
|
@ -15,63 +15,87 @@ private val RobotoFamily = FontFamily(
|
|||
Font(R.font.roboto_medium, FontWeight.Medium),
|
||||
)
|
||||
|
||||
val TangemTypography = Typography(
|
||||
defaultFontFamily = RobotoFamily,
|
||||
h1 = TextStyle(
|
||||
@Immutable
|
||||
data class TangemTypography internal constructor(
|
||||
val head: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 34.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
letterSpacing = TextUnit(value = 0f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 44f, type = TextUnitType.Sp),
|
||||
),
|
||||
val h1: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 34.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
letterSpacing = TextUnit(value = 0f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 44f, type = TextUnitType.Sp),
|
||||
),
|
||||
h2 = TextStyle(
|
||||
val h2: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(value = 0.18f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 32f, type = TextUnitType.Sp),
|
||||
),
|
||||
h3 = TextStyle(
|
||||
val h3: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(value = 0.15f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 24f, type = TextUnitType.Sp),
|
||||
),
|
||||
subtitle1 = TextStyle(
|
||||
val subtitle1: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(value = 0.15f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 24f, type = TextUnitType.Sp),
|
||||
),
|
||||
subtitle2 = TextStyle(
|
||||
val subtitle2: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(value = 0.5f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 24f, type = TextUnitType.Sp),
|
||||
),
|
||||
body1 = TextStyle(
|
||||
val body1: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
letterSpacing = TextUnit(value = 0.5f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 24f, type = TextUnitType.Sp),
|
||||
),
|
||||
body2 = TextStyle(
|
||||
val body2: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
letterSpacing = TextUnit(value = 0.25f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp),
|
||||
),
|
||||
button = TextStyle(
|
||||
val button: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp),
|
||||
),
|
||||
caption = TextStyle(
|
||||
val caption1: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(value = 0.4f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp),
|
||||
),
|
||||
val caption2: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
letterSpacing = TextUnit(value = 0.4f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp),
|
||||
),
|
||||
overline = TextStyle(
|
||||
val overline: TextStyle = TextStyle(
|
||||
fontFamily = RobotoFamily,
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
letterSpacing = TextUnit(value = 1.5f, type = TextUnitType.Sp),
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ internal fun getCoinIconUrl(blockchain: Blockchain): String? {
|
|||
internal fun List<UserTokensResponse.Token>.hasCoinForToken(token: CryptoCurrency.Token): Boolean {
|
||||
return any {
|
||||
val blockchain = getBlockchain(networkId = token.network.id)
|
||||
|
||||
it.id == blockchain.toCoinId()
|
||||
val tokenDerivation = token.network.derivationPath.value
|
||||
it.id == blockchain.toCoinId() && it.derivationPath == tokenDerivation
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.data.common)
|
||||
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.domain.legacy)
|
||||
|
|
@ -20,7 +22,8 @@ dependencies {
|
|||
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.jodatime)
|
||||
|
||||
implementation(deps.hilt.core)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.data.txhistory.di
|
||||
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.txhistory.repository.DefaultTxHistoryRepository
|
||||
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -17,10 +19,14 @@ internal object TxHistoryDataModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideTxHistoryRepository(
|
||||
cacheRegistry: CacheRegistry,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
txHistoryItemsStore: TxHistoryItemsStore,
|
||||
): TxHistoryRepository = DefaultTxHistoryRepository(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
userWalletsStore = userWalletsStore,
|
||||
cacheRegistry,
|
||||
walletManagersFacade,
|
||||
userWalletsStore,
|
||||
txHistoryItemsStore,
|
||||
)
|
||||
}
|
||||
|
|
@ -3,7 +3,9 @@ package com.tangem.data.txhistory.repository
|
|||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.txhistory.repository.paging.TxHistoryPagingSource
|
||||
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -13,50 +15,57 @@ import com.tangem.domain.txhistory.models.TxHistoryStateError
|
|||
import com.tangem.domain.txhistory.repository.TxHistoryRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class DefaultTxHistoryRepository(
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val txHistoryItemsStore: TxHistoryItemsStore,
|
||||
) : TxHistoryRepository {
|
||||
|
||||
override suspend fun getTxHistoryItemsCount(network: Network): Int {
|
||||
val userWallet = getUserWallet()
|
||||
override suspend fun getTxHistoryItemsCount(userWalletId: UserWalletId, network: Network): Int {
|
||||
val userWallet = getUserWallet(userWalletId)
|
||||
val state = walletManagersFacade.getTxHistoryState(
|
||||
userWalletId = userWallet.walletId,
|
||||
network = network,
|
||||
)
|
||||
return when (state) {
|
||||
is TxHistoryState.Failed.FetchError -> throw TxHistoryStateError.DataError(state.exception)
|
||||
TxHistoryState.NotImplemented -> throw TxHistoryStateError.TxHistoryNotImplemented
|
||||
TxHistoryState.Success.Empty -> throw TxHistoryStateError.EmptyTxHistories
|
||||
is TxHistoryState.NotImplemented -> throw TxHistoryStateError.TxHistoryNotImplemented
|
||||
is TxHistoryState.Success.Empty -> throw TxHistoryStateError.EmptyTxHistories
|
||||
is TxHistoryState.Success.HasTransactions -> state.txCount
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTxHistoryItems(currency: CryptoCurrency, pageSize: Int): Flow<PagingData<TxHistoryItem>> {
|
||||
val userWallet = getUserWallet()
|
||||
return Pager(
|
||||
override fun getTxHistoryItems(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
pageSize: Int,
|
||||
refresh: Boolean,
|
||||
): Flow<PagingData<TxHistoryItem>> {
|
||||
val pager = Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = pageSize,
|
||||
initialLoadSize = pageSize,
|
||||
),
|
||||
pagingSourceFactory = {
|
||||
TxHistoryPagingSource(
|
||||
loadPage = { page: Int, pageSize: Int ->
|
||||
walletManagersFacade.getTxHistoryItems(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = currency,
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
)
|
||||
},
|
||||
sourceParams = TxHistoryPagingSource.Params(userWalletId, currency, pageSize, refresh),
|
||||
txHistoryItemsStore = txHistoryItemsStore,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
cacheRegistry = cacheRegistry,
|
||||
)
|
||||
},
|
||||
).flow
|
||||
)
|
||||
|
||||
return pager.flow
|
||||
}
|
||||
|
||||
private fun getUserWallet(): UserWallet = requireNotNull(userWalletsStore.selectedUserWalletOrNull) {
|
||||
"Selected wallet must not be null"
|
||||
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
|
||||
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
|
||||
"Unable to find user wallet with provided ID: $userWalletId"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,34 +2,96 @@ package com.tangem.data.txhistory.repository.paging
|
|||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
|
||||
private const val INITIAL_PAGE = 1
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import timber.log.Timber
|
||||
|
||||
internal class TxHistoryPagingSource(
|
||||
private val loadPage: suspend (page: Int, pageSize: Int) -> PaginationWrapper<TxHistoryItem>,
|
||||
private val sourceParams: Params,
|
||||
private val txHistoryItemsStore: TxHistoryItemsStore,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
) : PagingSource<Int, TxHistoryItem>() {
|
||||
|
||||
private val storeKey = TxHistoryItemsStore.Key(sourceParams.userWalletId, sourceParams.currency)
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, TxHistoryItem>): Int? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(other = 1)
|
||||
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(other = 1)
|
||||
val anchorPage = state.closestPageToPosition(anchorPosition)
|
||||
anchorPage?.prevKey?.inc() ?: anchorPage?.nextKey?.dec()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TxHistoryItem> {
|
||||
val currentPage = params.key ?: INITIAL_PAGE
|
||||
return try {
|
||||
val result = loadPage(currentPage, params.loadSize)
|
||||
val pageToLoad = params.key ?: INITIAL_PAGE
|
||||
|
||||
LoadResult.Page(
|
||||
data = result.items,
|
||||
prevKey = if (currentPage > INITIAL_PAGE) currentPage.minus(1) else null,
|
||||
nextKey = if (result.page < result.totalPages) currentPage.plus(1) else null,
|
||||
return try {
|
||||
val wrappedItems = loadItems(
|
||||
pageToLoad = pageToLoad,
|
||||
pageSize = sourceParams.pageSize,
|
||||
refresh = sourceParams.refresh && params is LoadParams.Refresh,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
|
||||
val items = wrappedItems.items
|
||||
val prevPage = when {
|
||||
items.isEmpty() -> null
|
||||
pageToLoad > INITIAL_PAGE -> pageToLoad.dec()
|
||||
else -> null
|
||||
}
|
||||
val nextPage = when {
|
||||
items.isEmpty() -> INITIAL_PAGE
|
||||
pageToLoad < wrappedItems.totalPages -> pageToLoad.inc()
|
||||
else -> null
|
||||
}
|
||||
|
||||
LoadResult.Page(items, prevKey = prevPage, nextKey = nextPage)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Unable to load the transaction history for the requested page: $pageToLoad")
|
||||
|
||||
LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadItems(pageToLoad: Int, pageSize: Int, refresh: Boolean): PaginationWrapper<TxHistoryItem> {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getTxHistoryPageKey(pageToLoad),
|
||||
skipCache = refresh,
|
||||
block = { fetch(pageToLoad, pageSize) },
|
||||
)
|
||||
|
||||
return requireNotNull(txHistoryItemsStore.getSyncOrNull(storeKey, pageToLoad)) {
|
||||
"The transaction history page #$pageToLoad could not be retrieved"
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetch(pageToLoad: Int, pageSize: Int) {
|
||||
val wrappedItems = walletManagersFacade.getTxHistoryItems(
|
||||
userWalletId = sourceParams.userWalletId,
|
||||
currency = sourceParams.currency,
|
||||
page = pageToLoad,
|
||||
pageSize = pageSize,
|
||||
)
|
||||
|
||||
txHistoryItemsStore.store(storeKey, wrappedItems)
|
||||
}
|
||||
|
||||
private fun getTxHistoryPageKey(page: Int): String {
|
||||
return "tx_history_page_${sourceParams.currency}_${sourceParams.userWalletId}_$page"
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
val pageSize: Int,
|
||||
val refresh: Boolean,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
private const val INITIAL_PAGE = 1
|
||||
}
|
||||
}
|
||||
|
|
@ -212,6 +212,4 @@ private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5
|
|||
private val excludedBlockchains = listOf(
|
||||
Blockchain.Unknown,
|
||||
Blockchain.Ducatus,
|
||||
Blockchain.Telos, // disable in 4.9
|
||||
Blockchain.TelosTestnet, // disable in 4.9
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.domain.walletconnect
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class WalletConnectActions : Action {
|
||||
sealed class New {
|
||||
data class Initialize(val userWallet: UserWallet) : WalletConnectActions()
|
||||
|
||||
data class SetupUserChains(val userWallet: UserWallet) : WalletConnectActions()
|
||||
}
|
||||
}
|
||||
|
|
@ -221,6 +221,10 @@ class DefaultWalletManagersFacade(
|
|||
return walletManager
|
||||
}
|
||||
|
||||
override suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List<WalletManager> {
|
||||
return walletManagersStore.getAllSync(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun getAddress(userWalletId: UserWalletId, network: Network): List<Address> {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ interface WalletManagersFacade {
|
|||
derivationPath: String?,
|
||||
): WalletManager?
|
||||
|
||||
@Deprecated("Will be removed in future")
|
||||
suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List<WalletManager>
|
||||
|
||||
/**
|
||||
* Returns ordered list of addresses for selected wallet for given currency
|
||||
*
|
||||
|
|
|
|||
|
|
@ -9,11 +9,15 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
|
||||
implementation(projects.core.utils)
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/** Project - Other */
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Android - Other */
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
}
|
||||
|
|
@ -6,13 +6,19 @@ import com.tangem.domain.tokens.model.Network
|
|||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.models.TxHistoryListError
|
||||
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TxHistoryRepository {
|
||||
|
||||
@Throws(TxHistoryStateError::class)
|
||||
suspend fun getTxHistoryItemsCount(network: Network): Int
|
||||
suspend fun getTxHistoryItemsCount(userWalletId: UserWalletId, network: Network): Int
|
||||
|
||||
@Throws(TxHistoryListError::class)
|
||||
fun getTxHistoryItems(currency: CryptoCurrency, pageSize: Int): Flow<PagingData<TxHistoryItem>>
|
||||
fun getTxHistoryItems(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
pageSize: Int,
|
||||
refresh: Boolean,
|
||||
): Flow<PagingData<TxHistoryItem>>
|
||||
}
|
||||
|
|
@ -6,14 +6,15 @@ import arrow.core.raise.either
|
|||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
// TODO: Add tests
|
||||
class GetTxHistoryItemsCountUseCase(private val repository: TxHistoryRepository) {
|
||||
|
||||
// FIXME: Provide UserWalletId
|
||||
suspend operator fun invoke(network: Network): Either<TxHistoryStateError, Int> {
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either<TxHistoryStateError, Int> {
|
||||
return either {
|
||||
catch(
|
||||
block = { repository.getTxHistoryItemsCount(network) },
|
||||
block = { repository.getTxHistoryItemsCount(userWalletId, network) },
|
||||
catch = { throwable ->
|
||||
raise(
|
||||
when (throwable) {
|
||||
|
|
|
|||
|
|
@ -7,21 +7,25 @@ import com.tangem.domain.tokens.model.CryptoCurrency
|
|||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.models.TxHistoryListError
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
|
||||
private const val DEFAULT_PAGE_SIZE = 50
|
||||
|
||||
// TODO: Add tests
|
||||
class GetTxHistoryItemsUseCase(private val repository: TxHistoryRepository) {
|
||||
|
||||
// FIXME: Provide UserWalletId
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
pageSize: Int = DEFAULT_PAGE_SIZE,
|
||||
refresh: Boolean = false,
|
||||
): Either<TxHistoryListError, Flow<PagingData<TxHistoryItem>>> {
|
||||
return either {
|
||||
repository
|
||||
.getTxHistoryItems(currency = currency, pageSize = pageSize)
|
||||
.getTxHistoryItems(userWalletId, currency, pageSize, refresh)
|
||||
.catch { raise(TxHistoryListError.DataError(it)) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.domain.wallets.models
|
||||
|
||||
sealed interface GetSelectedWalletError {
|
||||
|
||||
object DataError : GetSelectedWalletError
|
||||
|
||||
object NoUserWalletSelected : GetSelectedWalletError
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.wallets.models
|
||||
|
||||
sealed class GetUserWalletError {
|
||||
|
||||
data class DataError(val cause: Throwable) : GetUserWalletError()
|
||||
|
||||
object UserWalletNotFound : GetUserWalletError()
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import arrow.core.Either
|
|||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.models.GetSelectedWalletError
|
||||
import com.tangem.domain.wallets.models.GetUserWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
||||
/**
|
||||
|
|
@ -17,16 +17,20 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
*/
|
||||
class GetSelectedWalletUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
|
||||
operator fun invoke(): Either<GetSelectedWalletError, UserWallet> {
|
||||
operator fun invoke(): Either<GetUserWalletError, UserWallet> {
|
||||
return either {
|
||||
val userWalletsListManager = ensureNotNull(
|
||||
value = walletsStateHolder.userWalletsListManager,
|
||||
raise = { GetSelectedWalletError.DataError },
|
||||
raise = {
|
||||
val error = IllegalStateException("User wallets list manager not initialized")
|
||||
|
||||
GetUserWalletError.DataError(error)
|
||||
},
|
||||
)
|
||||
|
||||
ensureNotNull(
|
||||
value = userWalletsListManager.selectedUserWalletSync,
|
||||
raise = { GetSelectedWalletError.NoUserWalletSelected },
|
||||
raise = { GetUserWalletError.UserWalletNotFound },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.models.GetUserWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
class GetUserWalletUseCase(private val walletsStateHolder: WalletsStateHolder) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<Any, UserWallet> = either {
|
||||
val userWalletsListManager = ensureNotNull(
|
||||
value = walletsStateHolder.userWalletsListManager,
|
||||
raise = {
|
||||
val error = IllegalStateException("User wallets list manager not initialized")
|
||||
|
||||
GetUserWalletError.DataError(error)
|
||||
},
|
||||
)
|
||||
|
||||
val userWallets = userWalletsListManager.userWallets.firstOrNull().orEmpty()
|
||||
|
||||
ensureNotNull(userWallets.firstOrNull { it.walletId == userWalletId }) {
|
||||
raise(GetUserWalletError.UserWalletNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ import com.tangem.core.ui.components.SpacerH4
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.learn2earn.impl.R
|
||||
import com.tangem.feature.learn2earn.presentation.ui.component.GradientCircle
|
||||
import com.tangem.feature.learn2earn.presentation.ui.state.MainScreenState
|
||||
|
|
@ -70,13 +69,13 @@ internal fun GetBonusView(state: MainScreenState, modifier: Modifier = Modifier)
|
|||
) {
|
||||
Text(
|
||||
text = state.description.title.resolveReference(),
|
||||
style = TangemTypography.body1,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary2,
|
||||
)
|
||||
SpacerH4()
|
||||
Text(
|
||||
text = state.description.subtitle.resolveReference(),
|
||||
style = TangemTypography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import com.tangem.core.ui.components.SecondaryButton
|
|||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH32
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.learn2earn.impl.R
|
||||
import com.tangem.feature.learn2earn.presentation.ui.component.GradientCircle
|
||||
|
||||
|
|
@ -106,7 +105,7 @@ private fun StoryDescription(headerText: String, bodyText: String, modifier: Mod
|
|||
Text(
|
||||
text = bodyText,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTypography.subtitle1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.feature.onboarding.di
|
||||
|
||||
import com.tangem.feature.onboarding.navigation.DefaultOnboardingRouter
|
||||
import com.tangem.feature.onboarding.navigation.OnboardingRouter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
internal object OnboardingRouterModule {
|
||||
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
fun provideOnboardingRouter(): OnboardingRouter {
|
||||
return DefaultOnboardingRouter()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.feature.onboarding.navigation
|
||||
|
||||
class DefaultOnboardingRouter : OnboardingRouter
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.feature.onboarding.navigation
|
||||
|
||||
/**
|
||||
* Onboarding router
|
||||
*/
|
||||
// TODO: Move to onboarding api module [REDACTED_JIRA]
|
||||
interface OnboardingRouter {
|
||||
|
||||
companion object {
|
||||
const val CAN_SKIP_BACKUP = "onboarding_wallet_can_skip_backup"
|
||||
}
|
||||
}
|
||||
|
|
@ -112,7 +112,7 @@ private fun PhraseBlock(state: ImportSeedPhraseState, modifier: Modifier = Modif
|
|||
Text(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
text = message,
|
||||
style = TangemTheme.typography.caption.copy(
|
||||
style = TangemTheme.typography.caption2.copy(
|
||||
color = TangemTheme.colors.text.warning,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ internal fun AgreementText(@StringRes firstPartResId: Int, onClick: () -> Unit)
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing54),
|
||||
style = TangemTheme.typography.caption.copy(textAlign = TextAlign.Center),
|
||||
style = TangemTheme.typography.caption2.copy(textAlign = TextAlign.Center),
|
||||
maxLines = 2,
|
||||
onClick = {
|
||||
val clickableSpanStyle = requireNotNull(agreementText.spanStyles.getOrNull(1))
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ private fun TokenItem(token: TokenToSelect, network: Network, onTokenClick: () -
|
|||
SpacerW2()
|
||||
Text(
|
||||
text = token.symbol,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
|
@ -160,7 +160,7 @@ private fun TokenItem(token: TokenToSelect, network: Network, onTokenClick: () -
|
|||
if (!token.available) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.swapping_token_not_available),
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
} else if (token.addedTokenBalanceData != null) {
|
||||
|
|
@ -177,7 +177,7 @@ private fun TokenItem(token: TokenToSelect, network: Network, onTokenClick: () -
|
|||
SpacerW2()
|
||||
Text(
|
||||
text = token.addedTokenBalanceData.amount.orEmpty(),
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ interface TokenDetailsRouter {
|
|||
fun getEntryFragment(): Fragment
|
||||
|
||||
companion object {
|
||||
const val USER_WALLET_ID_KEY = "token_details_user_wallet_id"
|
||||
const val CRYPTO_CURRENCY_KEY = "token_details_crypto_currency"
|
||||
}
|
||||
}
|
||||
|
|
@ -59,24 +59,22 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
return when (status.value) {
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
-> {
|
||||
TokenDetailsBalanceBlockState.Content(
|
||||
actionButtons = currentState.actionButtons,
|
||||
fiatBalance = formatFiatAmount(status.value, appCurrencyProvider()),
|
||||
cryptoBalance = formatCryptoAmount(status),
|
||||
)
|
||||
}
|
||||
is CryptoCurrencyStatus.Loading -> {
|
||||
TokenDetailsBalanceBlockState.Loading(currentState.actionButtons)
|
||||
}
|
||||
-> TokenDetailsBalanceBlockState.Content(
|
||||
actionButtons = currentState.actionButtons,
|
||||
fiatBalance = formatFiatAmount(status.value, appCurrencyProvider()),
|
||||
cryptoBalance = formatCryptoAmount(status),
|
||||
)
|
||||
is CryptoCurrencyStatus.NoAccount -> TokenDetailsBalanceBlockState.Content(
|
||||
actionButtons = currentState.actionButtons,
|
||||
fiatBalance = formatFiatAmount(status.value, appCurrencyProvider()),
|
||||
cryptoBalance = formatCryptoAmount(status),
|
||||
)
|
||||
is CryptoCurrencyStatus.Loading -> TokenDetailsBalanceBlockState.Loading(currentState.actionButtons)
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
-> {
|
||||
TokenDetailsBalanceBlockState.Error(currentState.actionButtons)
|
||||
}
|
||||
-> TokenDetailsBalanceBlockState.Error(currentState.actionButtons)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,13 +112,13 @@ private fun CryptoBalance(
|
|||
is TokenDetailsBalanceBlockState.Content -> Text(
|
||||
modifier = modifier,
|
||||
text = if (isBalanceHidden) STARS else state.cryptoBalance,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
is TokenDetailsBalanceBlockState.Error -> Text(
|
||||
modifier = modifier,
|
||||
text = if (isBalanceHidden) STARS else BigDecimalFormatter.EMPTY_BALANCE_SIGN,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ internal fun TokenInfoBlock(state: TokenInfoBlockState, modifier: Modifier = Mod
|
|||
) {
|
||||
Text(
|
||||
text = state.name,
|
||||
style = TangemTheme.typography.h1,
|
||||
style = TangemTheme.typography.head,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
NetworkInfoText(state.currency)
|
||||
|
|
@ -63,7 +63,7 @@ private fun NetworkInfoText(currency: TokenInfoBlockState.Currency) {
|
|||
Text(
|
||||
text = stringResource(id = R.string.common_main_network),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
is TokenInfoBlockState.Currency.Token -> {
|
||||
|
|
@ -74,7 +74,7 @@ private fun NetworkInfoText(currency: TokenInfoBlockState.Currency) {
|
|||
val state = extractNetwork(tokenCurrency = currency)
|
||||
Text(
|
||||
text = state.normalText,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
Icon(
|
||||
|
|
@ -85,7 +85,7 @@ private fun NetworkInfoText(currency: TokenInfoBlockState.Currency) {
|
|||
)
|
||||
Text(
|
||||
text = state.boldText,
|
||||
style = TangemTheme.typography.caption.copy(fontWeight = FontWeight.Medium),
|
||||
style = TangemTheme.typography.caption2.copy(fontWeight = FontWeight.Medium),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,9 @@ import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent
|
|||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.analytics.TokenScreenEvent
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
|
|
@ -48,7 +47,7 @@ import kotlin.properties.Delegates
|
|||
@HiltViewModel
|
||||
internal class TokenDetailsViewModel @Inject constructor(
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
|
|
@ -67,15 +66,18 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, TokenDetailsClickIntents {
|
||||
|
||||
private val userWalletId: UserWalletId = savedStateHandle.get<String>(TokenDetailsRouter.USER_WALLET_ID_KEY)
|
||||
?.let { stringValue -> UserWalletId(stringValue) }
|
||||
?: error("This screen can't open without `UserWalletId`")
|
||||
|
||||
private val cryptoCurrency: CryptoCurrency = savedStateHandle[TokenDetailsRouter.CRYPTO_CURRENCY_KEY]
|
||||
?: error("This screen can't open without CryptoCurrency")
|
||||
?: error("This screen can't open without `CryptoCurrency`")
|
||||
|
||||
var router by Delegates.notNull<InnerTokenDetailsRouter>()
|
||||
|
||||
private val marketPriceJobHolder = JobHolder()
|
||||
private val refreshStateJobHolder = JobHolder()
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
private var wallet by Delegates.notNull<UserWallet>()
|
||||
|
||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||
|
||||
|
|
@ -91,23 +93,14 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private set
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
getWallet()
|
||||
updateContent(selectedWallet = wallet)
|
||||
updateContent()
|
||||
handleBalanceHiding(owner)
|
||||
}
|
||||
|
||||
private fun getWallet() {
|
||||
getSelectedWalletUseCase()
|
||||
.fold(
|
||||
ifLeft = { error("Can not get selected wallet $it") },
|
||||
ifRight = { wallet = it },
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateContent(selectedWallet: UserWallet) {
|
||||
updateMarketPrice(selectedWallet = selectedWallet)
|
||||
private fun updateContent() {
|
||||
updateMarketPrice()
|
||||
updateTxHistory(refresh = false, showItemsLoading = true)
|
||||
updateWarnings(selectedWallet = selectedWallet)
|
||||
updateWarnings()
|
||||
}
|
||||
|
||||
private fun handleBalanceHiding(owner: LifecycleOwner) {
|
||||
|
|
@ -135,10 +128,10 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun updateWarnings(selectedWallet: UserWallet) {
|
||||
private fun updateWarnings() {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
getCurrencyWarningsUseCase.invoke(
|
||||
userWalletId = selectedWallet.walletId,
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
derivationPath = cryptoCurrency.network.derivationPath,
|
||||
)
|
||||
|
|
@ -148,9 +141,9 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateMarketPrice(selectedWallet: UserWallet) {
|
||||
private fun updateMarketPrice() {
|
||||
getCurrencyStatusUpdatesUseCase(
|
||||
userWalletId = selectedWallet.walletId,
|
||||
userWalletId = userWalletId,
|
||||
currencyId = cryptoCurrency.id,
|
||||
derivationPath = cryptoCurrency.network.derivationPath,
|
||||
)
|
||||
|
|
@ -159,7 +152,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
uiState = stateFactory.getCurrencyLoadedBalanceState(either)
|
||||
either.onRight { status ->
|
||||
cryptoCurrencyStatus = status
|
||||
updateButtons(userWalletId = selectedWallet.walletId, currencyStatus = status)
|
||||
updateButtons(userWalletId = userWalletId, currencyStatus = status)
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
|
|
@ -175,6 +168,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private fun updateTxHistory(refresh: Boolean, showItemsLoading: Boolean) {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
)
|
||||
|
||||
|
|
@ -184,9 +178,13 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
txHistoryItemsCountEither.onRight {
|
||||
val either = txHistoryItemsUseCase(currency = cryptoCurrency)
|
||||
.map { it.cachedIn(viewModelScope) }
|
||||
uiState = stateFactory.getLoadedTxHistoryState(txHistoryEither = either)
|
||||
val maybeTxHistory = txHistoryItemsUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
refresh = refresh,
|
||||
).map { it.cachedIn(viewModelScope) }
|
||||
|
||||
uiState = stateFactory.getLoadedTxHistoryState(maybeTxHistory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -211,13 +209,15 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
analyticsEventsHandler.send(TokenScreenEvent.ButtonBuy(cryptoCurrency.symbol))
|
||||
val status = cryptoCurrencyStatus ?: return
|
||||
|
||||
reduxStateHolder.dispatch(
|
||||
TradeCryptoAction.New.Buy(
|
||||
userWallet = wallet,
|
||||
cryptoCurrencyStatus = status,
|
||||
appCurrencyCode = selectedAppCurrencyFlow.value.code,
|
||||
),
|
||||
)
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
reduxStateHolder.dispatch(
|
||||
TradeCryptoAction.New.Buy(
|
||||
userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch },
|
||||
cryptoCurrencyStatus = status,
|
||||
appCurrencyCode = selectedAppCurrencyFlow.value.code,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReloadClick() {
|
||||
|
|
@ -231,38 +231,38 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatus ?: return
|
||||
|
||||
when (cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
reduxStateHolder.dispatch(
|
||||
action = TradeCryptoAction.New.SendCoin(
|
||||
userWallet = wallet,
|
||||
coinStatus = cryptoCurrencyStatus,
|
||||
),
|
||||
)
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
when (cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
reduxStateHolder.dispatch(
|
||||
action = TradeCryptoAction.New.SendCoin(
|
||||
userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch },
|
||||
coinStatus = cryptoCurrencyStatus,
|
||||
),
|
||||
)
|
||||
}
|
||||
is CryptoCurrency.Token -> sendToken(status = cryptoCurrencyStatus)
|
||||
}
|
||||
is CryptoCurrency.Token -> sendToken(status = cryptoCurrencyStatus)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendToken(status: CryptoCurrencyStatus) {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
getNetworkCoinStatusUseCase(
|
||||
userWalletId = wallet.walletId,
|
||||
val maybeCoinStatus = getNetworkCoinStatusUseCase(
|
||||
userWalletId = userWalletId,
|
||||
networkId = status.currency.network.id,
|
||||
derivationPath = status.currency.network.derivationPath,
|
||||
)
|
||||
.take(count = 1)
|
||||
.collectLatest {
|
||||
it.onRight { coinStatus ->
|
||||
reduxStateHolder.dispatch(
|
||||
action = TradeCryptoAction.New.SendToken(
|
||||
userWallet = wallet,
|
||||
tokenStatus = status,
|
||||
coinFiatRate = coinStatus.value.fiatRate,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
).firstOrNull()
|
||||
|
||||
maybeCoinStatus?.onRight { coinStatus ->
|
||||
reduxStateHolder.dispatch(
|
||||
action = TradeCryptoAction.New.SendToken(
|
||||
userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch },
|
||||
tokenStatus = status,
|
||||
coinFiatRate = coinStatus.value.fiatRate,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -271,7 +271,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val addresses = walletManagersFacade.getAddress(
|
||||
userWalletId = wallet.walletId,
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
)
|
||||
|
||||
|
|
@ -314,7 +314,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
analyticsEventsHandler.send(TokenScreenEvent.ButtonRemoveToken(cryptoCurrency.symbol))
|
||||
|
||||
viewModelScope.launch {
|
||||
val hasLinkedTokens = removeCurrencyUseCase.hasLinkedTokens(wallet.walletId, cryptoCurrency)
|
||||
val hasLinkedTokens = removeCurrencyUseCase.hasLinkedTokens(userWalletId, cryptoCurrency)
|
||||
uiState = if (hasLinkedTokens) {
|
||||
stateFactory.getStateWithLinkedTokensDialog(cryptoCurrency)
|
||||
} else {
|
||||
|
|
@ -325,7 +325,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
|
||||
override fun onHideConfirmed() {
|
||||
viewModelScope.launch {
|
||||
removeCurrencyUseCase.invoke(wallet.walletId, cryptoCurrency)
|
||||
removeCurrencyUseCase.invoke(userWalletId, cryptoCurrency)
|
||||
.onLeft { Timber.e(it) }
|
||||
.onRight { router.popBackStack() }
|
||||
}
|
||||
|
|
@ -335,7 +335,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
analyticsEventsHandler.send(TokenScreenEvent.ButtonExplore(cryptoCurrency.symbol))
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val addresses = walletManagersFacade.getAddress(
|
||||
userWalletId = wallet.walletId,
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
)
|
||||
|
||||
|
|
@ -357,7 +357,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
viewModelScope.launch {
|
||||
router.openUrl(
|
||||
url = getExploreUrlUseCase(
|
||||
userWalletId = wallet.walletId,
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
addressType = addressType,
|
||||
),
|
||||
|
|
@ -373,8 +373,8 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
viewModelScope.launch(dispatchers.io) {
|
||||
listOf(
|
||||
async {
|
||||
fetchCurrencyStatusUseCase.invoke(
|
||||
userWalletId = wallet.walletId,
|
||||
fetchCurrencyStatusUseCase(
|
||||
userWalletId = userWalletId,
|
||||
id = cryptoCurrency.id,
|
||||
derivationPath = cryptoCurrency.network.derivationPath,
|
||||
refresh = true,
|
||||
|
|
@ -386,7 +386,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
showItemsLoading = uiState.txHistoryState !is TxHistoryState.Content,
|
||||
)
|
||||
},
|
||||
async { updateWarnings(wallet) },
|
||||
async { updateWarnings() },
|
||||
).awaitAll()
|
||||
uiState = stateFactory.getRefreshedState()
|
||||
}.saveIn(refreshStateJobHolder)
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ dependencies {
|
|||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
|
||||
//TODO: Create api/impl modules for onboarding [REDACTED_JIRA]
|
||||
implementation(projects.features.onboarding)
|
||||
|
||||
/** Feature Apis */
|
||||
implementation(projects.features.wallet.api)
|
||||
implementation(projects.features.tokendetails.api)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ internal class WalletFragment : ComposeFragment() {
|
|||
setSystemBarsColor(systemBarsColor)
|
||||
}
|
||||
|
||||
_walletRouter.Initialize(fragmentManager = requireActivity().supportFragmentManager)
|
||||
_walletRouter.Initialize(onFinish = requireActivity()::finish)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -190,7 +190,13 @@ internal object WalletPreviewData {
|
|||
)
|
||||
}
|
||||
|
||||
val loadingTokenItemState by lazy { TokenItemState.Loading(id = "Loading#1") }
|
||||
val loadingTokenItemState by lazy {
|
||||
TokenItemState.Loading(
|
||||
id = "Loading#1",
|
||||
iconState = customTokenIconState.copy(isGrayscale = true),
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
)
|
||||
}
|
||||
|
||||
private const val networksSize = 10
|
||||
private const val tokensSize = 3
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
|
|
@ -73,6 +72,6 @@ private fun NonFiatContentText(@StringRes text: Int) {
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTypography.body2,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import com.tangem.common.Strings
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.CryptoAmountState as TokenCryptoAmountState
|
||||
|
||||
|
|
@ -49,7 +48,7 @@ private fun CryptoAmountText(amount: String, modifier: Modifier = Modifier) {
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTypography.body2,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import com.tangem.common.Strings
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.FiatAmountState as TokenFiatAmountState
|
||||
|
||||
@Composable
|
||||
|
|
@ -40,7 +39,7 @@ private fun FiatAmountText(text: String, modifier: Modifier = Modifier) {
|
|||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTypography.body2,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.core.ui.components.RectangleShimmer
|
|||
import com.tangem.core.ui.components.SpacerW4
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.PriceChangeState as TokenPriceChangeState
|
||||
|
|
@ -87,7 +86,7 @@ private fun PriceChangeText(type: PriceChangeType?, text: String?) {
|
|||
},
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTypography.body2,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemTypography
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TitleState as TokenTitleState
|
||||
|
||||
|
|
@ -63,7 +62,7 @@ private fun CurrencyNameText(name: String, modifier: Modifier = Modifier) {
|
|||
color = TangemTheme.colors.text.primary1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTypography.subtitle2,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ internal sealed class TokenItemState {
|
|||
abstract val priceChangeState: PriceChangeState?
|
||||
|
||||
/** Loading token state */
|
||||
data class Loading(override val id: String) : TokenItemState() {
|
||||
override val iconState: IconState = IconState.Loading
|
||||
override val titleState: TitleState = TitleState.Loading
|
||||
data class Loading(
|
||||
override val id: String,
|
||||
override val iconState: IconState,
|
||||
override val titleState: TitleState.Content,
|
||||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Loading
|
||||
override val cryptoAmountState: CryptoAmountState = CryptoAmountState.Loading
|
||||
override val priceChangeState: PriceChangeState = PriceChangeState.Loading
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import androidx.compose.ui.graphics.Brush
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -86,6 +88,7 @@ private fun TokenList(
|
|||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
Box(modifier = modifier) {
|
||||
val onDragEnd: (Int, Int) -> Unit = remember {
|
||||
{ _, _ ->
|
||||
|
|
@ -119,7 +122,10 @@ private fun TokenList(
|
|||
) { index, item ->
|
||||
|
||||
val onDragStart = remember(item) {
|
||||
{ dndConfig.onItemDragStart(item) }
|
||||
{
|
||||
dndConfig.onItemDragStart(item)
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
}
|
||||
|
||||
DraggableItem(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
|
|
@ -21,6 +20,7 @@ import com.tangem.core.navigation.NavigationAction
|
|||
import com.tangem.core.navigation.ReduxNavController
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.onboarding.navigation.OnboardingRouter
|
||||
import com.tangem.feature.wallet.presentation.WalletFragment
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
|
||||
|
|
@ -33,13 +33,13 @@ import kotlin.properties.Delegates
|
|||
internal class DefaultWalletRouter(private val reduxNavController: ReduxNavController) : InnerWalletRouter {
|
||||
|
||||
private var navController: NavHostController by Delegates.notNull()
|
||||
private var fragmentManager: FragmentManager by Delegates.notNull()
|
||||
private var onFinish: () -> Unit = {}
|
||||
|
||||
override fun getEntryFragment(): Fragment = WalletFragment.create()
|
||||
|
||||
@Composable
|
||||
override fun Initialize(fragmentManager: FragmentManager) {
|
||||
this.fragmentManager = fragmentManager
|
||||
override fun Initialize(onFinish: () -> Unit) {
|
||||
this.onFinish = onFinish
|
||||
|
||||
NavHost(
|
||||
navController = rememberNavController().apply { navController = this },
|
||||
|
|
@ -78,11 +78,11 @@ internal class DefaultWalletRouter(private val reduxNavController: ReduxNavContr
|
|||
* 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) {
|
||||
if (screen != null) {
|
||||
if (navController.currentBackStack.value.size == BACKSTACK_ENTRY_COUNT_TO_CLOSE_WALLET_SCREEN) {
|
||||
if (screen == AppScreen.Home) {
|
||||
reduxNavController.navigate(action = NavigationAction.PopBackTo(screen))
|
||||
} else {
|
||||
fragmentManager.popBackStack()
|
||||
onFinish.invoke()
|
||||
}
|
||||
} else {
|
||||
navController.popBackStack()
|
||||
|
|
@ -100,18 +100,26 @@ internal class DefaultWalletRouter(private val reduxNavController: ReduxNavContr
|
|||
}
|
||||
|
||||
override fun openOnboardingScreen() {
|
||||
reduxNavController.navigate(action = NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
reduxNavController.navigate(
|
||||
action = NavigationAction.NavigateTo(
|
||||
screen = AppScreen.OnboardingWallet,
|
||||
bundle = bundleOf(OnboardingRouter.CAN_SKIP_BACKUP to false),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun openTxHistoryWebsite(url: String) {
|
||||
reduxNavController.navigate(action = NavigationAction.OpenUrl(url))
|
||||
}
|
||||
|
||||
override fun openTokenDetails(currency: CryptoCurrency) {
|
||||
override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
reduxNavController.navigate(
|
||||
action = NavigationAction.NavigateTo(
|
||||
screen = AppScreen.WalletDetails,
|
||||
bundle = bundleOf(TokenDetailsRouter.CRYPTO_CURRENCY_KEY to currency),
|
||||
bundle = bundleOf(
|
||||
TokenDetailsRouter.USER_WALLET_ID_KEY to userWalletId.stringValue,
|
||||
TokenDetailsRouter.CRYPTO_CURRENCY_KEY to currency,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ 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.core.navigation.AppScreen
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -22,11 +21,11 @@ internal interface InnerWalletRouter : WalletRouter {
|
|||
/**
|
||||
* Initialize router
|
||||
*
|
||||
* @param fragmentManager fragment manager
|
||||
* @param onFinish finish activity callback
|
||||
*/
|
||||
@Suppress("TopLevelComposableFunctions")
|
||||
@Composable
|
||||
fun Initialize(fragmentManager: FragmentManager)
|
||||
fun Initialize(onFinish: () -> Unit)
|
||||
|
||||
/** Pop back stack */
|
||||
fun popBackStack(screen: AppScreen? = null)
|
||||
|
|
@ -44,7 +43,7 @@ internal interface InnerWalletRouter : WalletRouter {
|
|||
fun openTxHistoryWebsite(url: String)
|
||||
|
||||
/** Open token details screen */
|
||||
fun openTokenDetails(currency: CryptoCurrency)
|
||||
fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency)
|
||||
|
||||
/** Open stories screen */
|
||||
fun openStoriesScreen()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ internal sealed interface WalletCardState {
|
|||
/** Title */
|
||||
val title: String
|
||||
|
||||
val additionalInfo: TextReference?
|
||||
|
||||
/** Wallet image resource id */
|
||||
@get:DrawableRes
|
||||
val imageResId: Int?
|
||||
|
|
@ -41,10 +43,10 @@ internal sealed interface WalletCardState {
|
|||
data class Content(
|
||||
override val id: UserWalletId,
|
||||
override val title: String,
|
||||
override val additionalInfo: TextReference,
|
||||
override val imageResId: Int?,
|
||||
override val onRenameClick: (UserWalletId, String) -> Unit,
|
||||
override val onDeleteClick: (UserWalletId) -> Unit,
|
||||
val additionalInfo: TextReference,
|
||||
val cardCount: Int?,
|
||||
val balance: String,
|
||||
) : WalletCardState
|
||||
|
|
@ -54,18 +56,18 @@ internal sealed interface WalletCardState {
|
|||
*
|
||||
* @property id wallet id
|
||||
* @property title wallet name
|
||||
* @property additionalInfo wallet additional info
|
||||
* @property imageResId wallet image resource id
|
||||
* @property onRenameClick lambda be invoked when Rename button is clicked
|
||||
* @property onDeleteClick lambda be invoked when Delete button is clicked
|
||||
* @property additionalInfo wallet additional info
|
||||
*/
|
||||
data class LockedContent(
|
||||
override val id: UserWalletId,
|
||||
override val title: String,
|
||||
override val additionalInfo: TextReference,
|
||||
override val imageResId: Int?,
|
||||
override val onRenameClick: (UserWalletId, String) -> Unit,
|
||||
override val onDeleteClick: (UserWalletId) -> Unit,
|
||||
val additionalInfo: TextReference,
|
||||
) : WalletCardState
|
||||
|
||||
/**
|
||||
|
|
@ -83,7 +85,9 @@ internal sealed interface WalletCardState {
|
|||
override val imageResId: Int?,
|
||||
override val onRenameClick: (UserWalletId, String) -> Unit,
|
||||
override val onDeleteClick: (UserWalletId) -> Unit,
|
||||
) : WalletCardState
|
||||
) : WalletCardState {
|
||||
override val additionalInfo: TextReference = EMPTY_BALANCE_TEXT
|
||||
}
|
||||
|
||||
/**
|
||||
* Wallet card loading state
|
||||
|
|
@ -97,6 +101,7 @@ internal sealed interface WalletCardState {
|
|||
data class Loading(
|
||||
override val id: UserWalletId,
|
||||
override val title: String,
|
||||
override val additionalInfo: TextReference? = null,
|
||||
override val imageResId: Int?,
|
||||
override val onRenameClick: (UserWalletId, String) -> Unit,
|
||||
override val onDeleteClick: (UserWalletId) -> Unit,
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ internal class WalletSkeletonStateConverter(
|
|||
return WalletCardState.Loading(
|
||||
id = walletId,
|
||||
title = name,
|
||||
additionalInfo = if (isMultiCurrency) WalletAdditionalInfoFactory.resolve(wallet = this) else null,
|
||||
imageResId = createImageResId(),
|
||||
onRenameClick = clickIntents::onRenameClick,
|
||||
onDeleteClick = clickIntents::onDeleteClick,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ internal fun WalletCard(state: WalletCardState, isBalanceHidden: Boolean, modifi
|
|||
)
|
||||
|
||||
AdditionalInfo(
|
||||
text = resolveAdditionalTextByState(state),
|
||||
text = state.additionalInfo,
|
||||
modifier = Modifier.constrainAs(additionalTextRef) {
|
||||
start.linkTo(parent.start)
|
||||
top.linkTo(balanceRef.bottom)
|
||||
|
|
@ -345,15 +345,6 @@ private fun AdditionalInfo(text: TextReference?, modifier: Modifier = Modifier)
|
|||
}
|
||||
}
|
||||
|
||||
private fun resolveAdditionalTextByState(state: WalletCardState): TextReference? {
|
||||
return when (state) {
|
||||
is WalletCardState.Content -> state.additionalInfo
|
||||
is WalletCardState.LockedContent -> state.additionalInfo
|
||||
is WalletCardState.Error -> WalletCardState.EMPTY_BALANCE_TEXT
|
||||
is WalletCardState.Loading -> null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AdditionalInfoText(text: TextReference) {
|
||||
Text(
|
||||
|
|
@ -361,7 +352,7 @@ private fun AdditionalInfoText(text: TextReference) {
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) {
|
|||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing48),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography.caption,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,24 +16,31 @@ internal class CryptoCurrencyStatusToTokenItemConverter(
|
|||
private val clickIntents: WalletClickIntents,
|
||||
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||
|
||||
private val iconStateConverter = CryptoCurrencyToIconStateConverter()
|
||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||
|
||||
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
|
||||
return when (value.value) {
|
||||
is CryptoCurrencyStatus.Loading -> TokenItemState.Loading(id = value.currency.id.value)
|
||||
is CryptoCurrencyStatus.Loading -> value.mapToLoadingState()
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> value.mapToTokenItemState()
|
||||
is CryptoCurrencyStatus.MissedDerivation -> value.mapToNoAddressTokenItemState()
|
||||
// TODO: Add other token item states, currently not designed
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> value.mapToUnreachableTokenItemState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToLoadingState(): TokenItemState.Loading {
|
||||
return TokenItemState.Loading(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(value = this),
|
||||
titleState = TokenItemState.TitleState.Content(text = currency.name),
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToTokenItemState(): TokenItemState.Content {
|
||||
return TokenItemState.Content(
|
||||
id = currency.id.value,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ internal class FiatBalanceToWalletCardConverter(
|
|||
}
|
||||
|
||||
private fun WalletCardState.toLoadingWalletCardState(): WalletCardState {
|
||||
return WalletCardState.Loading(id, title, imageResId, onRenameClick, onDeleteClick)
|
||||
return WalletCardState.Loading(id, title, additionalInfo, imageResId, onRenameClick, onDeleteClick)
|
||||
}
|
||||
|
||||
private fun WalletCardState.toErrorWalletCardState(): WalletCardState {
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.utils
|
||||
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal object LoadingItemsProvider {
|
||||
|
||||
fun getLoadingMultiCurrencyTokens(): ImmutableList<WalletTokensListState.TokensListItemState.Token> {
|
||||
val items = mutableListOf<WalletTokensListState.TokensListItemState.Token>()
|
||||
repeat(times = 5) {
|
||||
items.add(
|
||||
WalletTokensListState.TokensListItemState.Token(
|
||||
state = TokenItemState.Loading(id = "Loading#$it"),
|
||||
),
|
||||
)
|
||||
}
|
||||
return items.toImmutableList()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels
|
|||
|
||||
import androidx.lifecycle.*
|
||||
import androidx.paging.cachedIn
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
|
@ -39,6 +40,7 @@ import com.tangem.domain.redux.LegacyAction
|
|||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -48,6 +50,7 @@ import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent
|
|||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.userwallets.UserWalletBuilder
|
||||
import com.tangem.domain.walletconnect.WalletConnectActions
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -156,6 +159,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
private var singleWalletCryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
|
||||
private val tokensJobHolder = JobHolder()
|
||||
private val updateWcJobHolder = JobHolder()
|
||||
private val marketPriceJobHolder = JobHolder()
|
||||
private val buttonsJobHolder = JobHolder()
|
||||
private val notificationsJobHolder = JobHolder()
|
||||
|
|
@ -453,6 +457,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
* If jobs aren't stopped and wallet is changed then it will update state for the prev wallet.
|
||||
*/
|
||||
tokensJobHolder.update(job = null)
|
||||
updateWcJobHolder.update(job = null)
|
||||
marketPriceJobHolder.update(job = null)
|
||||
buttonsJobHolder.update(job = null)
|
||||
notificationsJobHolder.update(job = null)
|
||||
|
|
@ -719,7 +724,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
|
||||
override fun onTokenItemClick(currency: CryptoCurrency) {
|
||||
analyticsEventsHandler.send(PortfolioEvent.TokenTapped)
|
||||
router.openTokenDetails(currency = currency)
|
||||
router.openTokenDetails(getSelectedWallet().walletId, currency)
|
||||
}
|
||||
|
||||
override fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
|
|
@ -849,6 +854,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
* If jobs aren't stopped and wallet is changed then it will update state for the prev wallet.
|
||||
*/
|
||||
tokensJobHolder.update(job = null)
|
||||
updateWcJobHolder.update(job = null)
|
||||
marketPriceJobHolder.update(job = null)
|
||||
buttonsJobHolder.update(job = null)
|
||||
notificationsJobHolder.update(job = null)
|
||||
|
|
@ -860,17 +866,22 @@ internal class WalletViewModel @Inject constructor(
|
|||
wallet.isLocked -> {
|
||||
uiState = stateFactory.getLockedState()
|
||||
}
|
||||
wallet.isMultiCurrency -> getMultiCurrencyContent(index)
|
||||
wallet.isMultiCurrency -> getMultiCurrencyContent(wallet, index)
|
||||
!wallet.isMultiCurrency -> getSingleCurrencyContent(index)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMultiCurrencyContent(walletIndex: Int) {
|
||||
private fun getMultiCurrencyContent(wallet: UserWallet, walletIndex: Int) {
|
||||
val state = requireNotNull(uiState as? WalletMultiCurrencyState) {
|
||||
"Impossible to get a token list updates if state isn't WalletMultiCurrencyState"
|
||||
}
|
||||
|
||||
getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[walletIndex].id)
|
||||
val tokenListFlow = getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[walletIndex].id)
|
||||
.shareIn(viewModelScope, SharingStarted.WhileSubscribed())
|
||||
|
||||
initAndSetupWc(tokenListFlow, wallet)
|
||||
|
||||
tokenListFlow
|
||||
.distinctUntilChanged()
|
||||
.onEach { maybeTokenList ->
|
||||
uiState = stateFactory.getStateByTokensList(maybeTokenList)
|
||||
|
|
@ -903,6 +914,44 @@ internal class WalletViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun initAndSetupWc(tokenListFlow: SharedFlow<Either<TokenListError, TokenList>>, wallet: UserWallet) {
|
||||
initWalletConnectForWallet(wallet)
|
||||
tokenListFlow
|
||||
.filter(::filterLoadedTokenList)
|
||||
.take(1)
|
||||
.onEach {
|
||||
it.onRight {
|
||||
setupWalletConnectOnWallet(wallet)
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(viewModelScope)
|
||||
.saveIn(updateWcJobHolder)
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.isAllCurrenciesLoaded(): Boolean {
|
||||
return !this.any { it.value is CryptoCurrencyStatus.Loading }
|
||||
}
|
||||
|
||||
private fun filterLoadedTokenList(either: Either<TokenListError, TokenList>): Boolean {
|
||||
return either.fold(
|
||||
ifRight = { list ->
|
||||
when (list) {
|
||||
is TokenList.Ungrouped -> {
|
||||
list.currencies.isAllCurrenciesLoaded()
|
||||
}
|
||||
is TokenList.GroupedByNetwork -> {
|
||||
list.groups.flatMap { group -> group.currencies }.isAllCurrenciesLoaded()
|
||||
}
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
}
|
||||
},
|
||||
ifLeft = { false },
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.hasNonZeroWallets(): Boolean {
|
||||
return any {
|
||||
val amount = it.value.amount ?: return@any false
|
||||
|
|
@ -912,7 +961,7 @@ internal class WalletViewModel @Inject constructor(
|
|||
|
||||
private fun getSingleCurrencyContent(index: Int) {
|
||||
val wallet = getWallet(index)
|
||||
getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = wallet.walletId)
|
||||
getPrimaryCurrencyStatusUpdatesUseCase(wallet.walletId)
|
||||
.distinctUntilChanged()
|
||||
.onEach { maybeCryptoCurrencyStatus ->
|
||||
uiState = stateFactory.getSingleCurrencyLoadedBalanceState(maybeCryptoCurrencyStatus)
|
||||
|
|
@ -925,8 +974,8 @@ internal class WalletViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
updateNotifications(index)
|
||||
updateButtons(userWalletId = wallet.walletId, currencyStatus = status)
|
||||
updateTxHistory(status.currency)
|
||||
updateButtons(wallet.walletId, status)
|
||||
updateTxHistory(wallet.walletId, status.currency, refresh = false)
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
|
|
@ -934,9 +983,12 @@ internal class WalletViewModel @Inject constructor(
|
|||
.saveIn(marketPriceJobHolder)
|
||||
}
|
||||
|
||||
private fun updateTxHistory(currency: CryptoCurrency) {
|
||||
private fun updateTxHistory(userWalletId: UserWalletId, currency: CryptoCurrency, refresh: Boolean) {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(currency.network)
|
||||
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = currency.network,
|
||||
)
|
||||
|
||||
uiState = stateFactory.getLoadingTxHistoryState(
|
||||
itemsCountEither = txHistoryItemsCountEither,
|
||||
|
|
@ -944,7 +996,11 @@ internal class WalletViewModel @Inject constructor(
|
|||
|
||||
txHistoryItemsCountEither.onRight {
|
||||
uiState = stateFactory.getLoadedTxHistoryState(
|
||||
txHistoryEither = txHistoryItemsUseCase(currency = currency).map {
|
||||
txHistoryEither = txHistoryItemsUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
refresh = refresh,
|
||||
).map {
|
||||
it.cachedIn(viewModelScope)
|
||||
},
|
||||
)
|
||||
|
|
@ -1006,6 +1062,10 @@ internal class WalletViewModel @Inject constructor(
|
|||
|
||||
uiState = stateFactory.getRefreshedState()
|
||||
uiState = result.fold(stateFactory::getStateByCurrencyStatusError) { uiState }
|
||||
|
||||
singleWalletCryptoCurrencyStatus?.let {
|
||||
updateTxHistory(wallet.walletId, it.currency, refresh = true)
|
||||
}
|
||||
}.saveIn(refreshContentJobHolder)
|
||||
}
|
||||
|
||||
|
|
@ -1021,6 +1081,18 @@ internal class WalletViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun initWalletConnectForWallet(userWallet: UserWallet) {
|
||||
reduxStateHolder.dispatch(
|
||||
WalletConnectActions.New.Initialize(userWallet = userWallet),
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupWalletConnectOnWallet(userWallet: UserWallet) {
|
||||
reduxStateHolder.dispatch(
|
||||
WalletConnectActions.New.SetupUserChains(userWallet = userWallet),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getWallet(index: Int): UserWallet {
|
||||
return requireNotNull(
|
||||
value = wallets.getOrNull(index),
|
||||
|
|
@ -1028,5 +1100,12 @@ internal class WalletViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getSelectedWallet(): UserWallet {
|
||||
val state = uiState as? WalletState.ContentState
|
||||
?: error("Unable to get selected user wallet")
|
||||
|
||||
return getWallet(state.walletsListConfig.selectedWalletIndex)
|
||||
}
|
||||
|
||||
private fun getCardTypeResolver(index: Int): CardTypesResolver = getWallet(index).scanResponse.cardTypesResolver
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ kotlin = "1.8.21"
|
|||
# endregion Classpath
|
||||
|
||||
# region AndroidX
|
||||
androidxActivityCompose = "1.5.0"
|
||||
androidxActivityCompose = "1.8.0"
|
||||
androidxAppCompat = "1.5.1"
|
||||
androidxBrowser = "1.4.0"
|
||||
androidxConstraintLayout = "2.1.4"
|
||||
|
|
@ -24,16 +24,16 @@ androidx-palette = "1.0.0"
|
|||
|
||||
# region Compose
|
||||
compose-compiler = "1.4.7"
|
||||
compose-runtime = "1.4.3"
|
||||
compose-foundation = "1.4.3"
|
||||
compose-material = "1.4.3"
|
||||
compose-material3 = "1.1.0"
|
||||
compose-runtime = "1.5.3"
|
||||
compose-foundation = "1.5.3"
|
||||
compose-material = "1.5.3"
|
||||
compose-material3 = "1.1.2"
|
||||
compose-constraint = "1.0.1"
|
||||
compose-navigation = "2.5.3"
|
||||
compose-navigation = "2.7.4"
|
||||
compose-accompanist = "0.30.1"
|
||||
compose-paging = "3.2.0"
|
||||
compose-paging = "3.2.1"
|
||||
compose-reorderable = "0.9.6"
|
||||
compoese-lifecycle-runtime = "2.6.1"
|
||||
compoese-lifecycle-runtime = "2.6.2"
|
||||
# endregion Compose
|
||||
|
||||
# region Other libraries
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ internal object AppConfig {
|
|||
const val versionCode = 1
|
||||
const val versionName = "1.0.0-SNAPSHOT"
|
||||
const val minSdkVersion = 23
|
||||
const val targetSdkVersion = 33
|
||||
const val compileSdkVersion = 33
|
||||
const val targetSdkVersion = 34
|
||||
const val compileSdkVersion = 34
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue