Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-10 12:45:26 +03:00
commit 6ad667d49d
77 changed files with 880 additions and 343 deletions

View file

@ -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 {

View file

@ -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) {

View file

@ -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,
)
}
}

View file

@ -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()

View file

@ -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,
)
}
}
}
}

View file

@ -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,
)
}

View file

@ -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)
}

View file

@ -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,
)

View file

@ -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))
}
}

View file

@ -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,
)
}

View file

@ -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,
)

View file

@ -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)

View file

@ -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(),
)
}
}

View file

@ -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)
}
}

View file

@ -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,
)
}

View file

@ -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)

View file

@ -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()

View file

@ -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,
)
}

View file

@ -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,
)
}
}

View file

@ -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

View file

@ -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) },
)
}

View file

@ -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,
)
}
}

View file

@ -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 -> {

View file

@ -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 {

View file

@ -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),

View file

@ -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
}
}

View file

@ -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)

View file

@ -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,
)
}

View file

@ -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"
}
}
}

View file

@ -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
}
}

View file

@ -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
)

View file

@ -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()
}
}

View file

@ -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)

View file

@ -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
*

View file

@ -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)
}

View file

@ -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>>
}

View file

@ -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) {

View file

@ -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)) }
}
}

View file

@ -1,8 +0,0 @@
package com.tangem.domain.wallets.models
sealed interface GetSelectedWalletError {
object DataError : GetSelectedWalletError
object NoUserWalletSelected : GetSelectedWalletError
}

View file

@ -0,0 +1,8 @@
package com.tangem.domain.wallets.models
sealed class GetUserWalletError {
data class DataError(val cause: Throwable) : GetUserWalletError()
object UserWalletNotFound : GetUserWalletError()
}

View file

@ -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 },
)
}
}

View file

@ -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)
}
}
}

View file

@ -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,
)
}

View file

@ -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,
)
}

View file

@ -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()
}
}

View file

@ -0,0 +1,3 @@
package com.tangem.feature.onboarding.navigation
class DefaultOnboardingRouter : OnboardingRouter

View file

@ -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"
}
}

View file

@ -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,
),
)

View file

@ -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))

View file

@ -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,
)
}

View file

@ -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"
}
}

View file

@ -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)
}
}

View file

@ -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,
)
}

View file

@ -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,
)
}

View file

@ -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)

View file

@ -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)

View file

@ -38,7 +38,7 @@ internal class WalletFragment : ComposeFragment() {
setSystemBarsColor(systemBarsColor)
}
_walletRouter.Initialize(fragmentManager = requireActivity().supportFragmentManager)
_walletRouter.Initialize(onFinish = requireActivity()::finish)
}
companion object {

View file

@ -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

View file

@ -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,
)
}

View file

@ -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,
)
}

View file

@ -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,
)
}

View file

@ -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,
)
}
}

View file

@ -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,
)
}

View file

@ -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

View file

@ -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(

View file

@ -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,
),
),
)
}

View file

@ -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()

View file

@ -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,

View file

@ -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,

View file

@ -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,
)
}

View file

@ -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,
)
}
}

View file

@ -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,

View file

@ -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 {

View file

@ -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()
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}