Updated on 2026-08-14
This commit is contained in:
parent
d863d37629
commit
080592f9c7
3 changed files with 210 additions and 82 deletions
|
|
@ -9,11 +9,16 @@ internal data class AccountsUM(
|
|||
val onBackClick: () -> Unit,
|
||||
val walletSelector: WalletSelector,
|
||||
val accountListBottomSheetConfig: AccountListBottomSheetConfig,
|
||||
val onAccountsClick: () -> Boolean,
|
||||
val onFetchAccountsClick: () -> Unit,
|
||||
val onClearETagClick: () -> Unit,
|
||||
val buttons: ImmutableList<Button>,
|
||||
) {
|
||||
|
||||
data class Button(
|
||||
val title: String,
|
||||
val isInProgress: Boolean = false,
|
||||
val isEnabled: Boolean = true,
|
||||
val onClick: (title: String) -> Unit,
|
||||
)
|
||||
|
||||
data class WalletSelector(
|
||||
val selected: UserWallet?,
|
||||
val wallets: ImmutableList<UserWallet>,
|
||||
|
|
@ -21,6 +26,8 @@ internal data class AccountsUM(
|
|||
) : TangemBottomSheetConfigContent
|
||||
|
||||
data class AccountListBottomSheetConfig(
|
||||
val isAccountsShown: Boolean,
|
||||
val accounts: ImmutableList<Account.CryptoPortfolio>,
|
||||
val onDismiss: () -> Unit,
|
||||
) : TangemBottomSheetConfigContent
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.feature.tester.presentation.accounts.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -9,13 +7,14 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
|
|
@ -31,6 +30,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.tester.impl.R
|
||||
import com.tangem.feature.tester.presentation.accounts.entity.AccountsUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
|
|
@ -38,7 +38,7 @@ internal fun AccountsScreen(state: AccountsUM, modifier: Modifier = Modifier) {
|
|||
BackHandler(onBack = state.onBackClick)
|
||||
|
||||
var isWalletSelectorShown by remember { mutableStateOf(false) }
|
||||
var isAccountListShown by remember { mutableStateOf(false) }
|
||||
val isAccountListShown by rememberUpdatedState(state.accountListBottomSheetConfig.isAccountsShown)
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
|
|
@ -59,18 +59,7 @@ internal fun AccountsScreen(state: AccountsUM, modifier: Modifier = Modifier) {
|
|||
}
|
||||
|
||||
if (state.walletSelector.selected != null) {
|
||||
ManageAccountsButtons(
|
||||
state = state,
|
||||
onAccountsClick = { context ->
|
||||
val isEmpty = state.onAccountsClick()
|
||||
|
||||
if (!isEmpty) {
|
||||
isAccountListShown = true
|
||||
} else {
|
||||
Toast.makeText(context, "No accounts found", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
},
|
||||
)
|
||||
ManageAccountsButtons(buttons = state.buttons)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +71,7 @@ internal fun AccountsScreen(state: AccountsUM, modifier: Modifier = Modifier) {
|
|||
|
||||
AccountList(
|
||||
isShown = isAccountListShown,
|
||||
onDismissRequest = { isAccountListShown = false },
|
||||
onDismissRequest = state.accountListBottomSheetConfig.onDismiss,
|
||||
content = state.accountListBottomSheetConfig,
|
||||
)
|
||||
}
|
||||
|
|
@ -190,57 +179,39 @@ private fun AccountList(
|
|||
),
|
||||
titleText = stringReference("Accounts"),
|
||||
) { content: AccountsUM.AccountListBottomSheetConfig ->
|
||||
content.accounts.fastForEachIndexed { index, account ->
|
||||
val accountName = account.accountName.toUM().value.resolveReference()
|
||||
Text(
|
||||
text = "#${account.derivationIndex.value} – $accountName",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 12.dp),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
LazyColumn {
|
||||
itemsIndexed(content.accounts) { index, account ->
|
||||
val accountName = account.accountName.toUM().value.resolveReference()
|
||||
Text(
|
||||
text = "$accountName [#${account.derivationIndex.value}]",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 12.dp),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
if (index == content.accounts.lastIndex) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
} else {
|
||||
DividerWithPadding(start = 16.dp, end = 16.dp)
|
||||
if (index == content.accounts.lastIndex) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
} else {
|
||||
DividerWithPadding(start = 16.dp, end = 16.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("FunctionNaming")
|
||||
private fun LazyListScope.ManageAccountsButtons(state: AccountsUM, onAccountsClick: (Context) -> Unit) {
|
||||
item {
|
||||
val context = LocalContext.current
|
||||
|
||||
private fun LazyListScope.ManageAccountsButtons(buttons: ImmutableList<AccountsUM.Button>) {
|
||||
items(buttons) { button ->
|
||||
PrimaryButton(
|
||||
text = stringResourceSafe(R.string.accounts),
|
||||
onClick = { onAccountsClick(context) },
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PrimaryButton(
|
||||
text = "Fetch accounts",
|
||||
onClick = state.onFetchAccountsClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PrimaryButton(
|
||||
text = "Clear ETag",
|
||||
onClick = state.onClearETagClick,
|
||||
text = button.title,
|
||||
onClick = { button.onClick(button.title) },
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
showProgress = button.isInProgress,
|
||||
enabled = button.isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,30 +2,38 @@ package com.tangem.feature.tester.presentation.accounts.viewmodel
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.data.common.cache.etag.ETagsStore
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.tester.presentation.accounts.entity.AccountsUM
|
||||
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltViewModel
|
||||
internal class TesterAccountsViewModel @Inject constructor(
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val singleAccountListFetcher: SingleAccountListFetcher,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val eTagsStore: ETagsStore,
|
||||
|
|
@ -50,6 +58,8 @@ internal class TesterAccountsViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
userWallets.firstOrNull()?.let(::onWalletSelect)
|
||||
|
||||
userWallets.mapIndexed { index, userWallet ->
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId = userWallet.walletId))
|
||||
.distinctUntilChanged()
|
||||
|
|
@ -79,11 +89,26 @@ internal class TesterAccountsViewModel @Inject constructor(
|
|||
onWalletSelect = ::onWalletSelect,
|
||||
),
|
||||
accountListBottomSheetConfig = AccountsUM.AccountListBottomSheetConfig(
|
||||
isAccountsShown = false,
|
||||
accounts = persistentListOf(),
|
||||
onDismiss = {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
accountListBottomSheetConfig = it.accountListBottomSheetConfig.copy(
|
||||
isAccountsShown = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
buttons = persistentListOf(
|
||||
AccountsUM.Button(title = "Show the account list") { updateAccountsList() },
|
||||
AccountsUM.Button(title = "Fetch accounts", onClick = ::fetchAccounts),
|
||||
AccountsUM.Button(title = "Fill out the list (up to 20)", onClick = ::fillOutAccountList),
|
||||
AccountsUM.Button(title = "Archive all", onClick = ::archiveAllAccounts),
|
||||
// AccountsUM.Button(title = "Sort by derivation index", onClick = ::sortAccountsByIndex),
|
||||
AccountsUM.Button(title = "Clear ETag") { clearETag() },
|
||||
),
|
||||
onAccountsClick = ::updateAccountsList,
|
||||
onFetchAccountsClick = ::fetchAccounts,
|
||||
onClearETagClick = ::clearETag,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -92,48 +117,173 @@ internal class TesterAccountsViewModel @Inject constructor(
|
|||
it.copy(
|
||||
walletSelector = it.walletSelector.copy(selected = userWallet),
|
||||
accountListBottomSheetConfig = it.accountListBottomSheetConfig.copy(
|
||||
accounts = walletAccounts.value[userWallet.walletId]?.accounts
|
||||
?.filterIsInstance<Account.CryptoPortfolio>()
|
||||
?.toImmutableList()
|
||||
?: persistentListOf(),
|
||||
accounts = getWalletAccounts(userWallet.walletId),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAccountsList(): Boolean {
|
||||
val userWalletId = uiState.value.walletSelector.selected?.walletId ?: return false
|
||||
|
||||
val accounts = walletAccounts.value[userWalletId]?.accounts
|
||||
?.filterIsInstance<Account.CryptoPortfolio>()
|
||||
?.toImmutableList()
|
||||
?: persistentListOf()
|
||||
private fun updateAccountsList() {
|
||||
val userWalletId = getUserWallet()?.walletId ?: return
|
||||
val accounts = getWalletAccounts(userWalletId)
|
||||
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
accountListBottomSheetConfig = state.accountListBottomSheetConfig.copy(
|
||||
isAccountsShown = true,
|
||||
accounts = accounts,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return accounts.isEmpty()
|
||||
}
|
||||
|
||||
private fun fetchAccounts() {
|
||||
private fun fetchAccounts(title: String) {
|
||||
viewModelScope.launch {
|
||||
val userWalletId = uiState.value.walletSelector.selected?.walletId ?: return@launch
|
||||
|
||||
singleAccountListFetcher(
|
||||
params = SingleAccountListFetcher.Params(userWalletId = userWalletId),
|
||||
)
|
||||
toggleButtonProgress(title = title, isInProgress = true)
|
||||
|
||||
withContext(dispatchers.default) {
|
||||
singleAccountListFetcher(
|
||||
params = SingleAccountListFetcher.Params(userWalletId = userWalletId),
|
||||
)
|
||||
}
|
||||
|
||||
toggleButtonProgress(title = title, isInProgress = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearETag() {
|
||||
viewModelScope.launch {
|
||||
val userWallet = uiState.value.walletSelector.selected ?: return@launch
|
||||
eTagsStore.clear(userWalletId = userWallet.walletId, key = ETagsStore.Key.WalletAccounts)
|
||||
val userWalletId = getUserWallet()?.walletId ?: return@launch
|
||||
eTagsStore.clear(userWalletId = userWalletId, key = ETagsStore.Key.WalletAccounts)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fillOutAccountList(title: String) {
|
||||
val userWalletId = getUserWallet()?.walletId ?: return
|
||||
|
||||
viewModelScope.launch {
|
||||
toggleButtonProgress(title = title, isInProgress = true)
|
||||
|
||||
withContext(dispatchers.default) {
|
||||
var accountList = walletAccounts.value[userWalletId] ?: return@withContext
|
||||
val occupiedIndexes = accountList.accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
.map { it.derivationIndex.value }
|
||||
.toSet()
|
||||
|
||||
var nextIndex = 0
|
||||
@Suppress("LoopWithTooManyJumpStatements") // never mind for Tester Menu
|
||||
while (accountList.canAddMoreAccounts) {
|
||||
while (occupiedIndexes.contains(nextIndex)) {
|
||||
nextIndex++
|
||||
}
|
||||
|
||||
val derivationIndex = DerivationIndex(nextIndex).getOrNull() ?: break
|
||||
|
||||
val newAccount = Account.CryptoPortfolio.invoke(
|
||||
accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex),
|
||||
name = "Account #$nextIndex",
|
||||
icon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
derivationIndex = nextIndex,
|
||||
cryptoCurrencies = emptySet(),
|
||||
)
|
||||
.getOrNull()
|
||||
?: break
|
||||
|
||||
(accountList + newAccount)
|
||||
.onRight { accountList = it }
|
||||
.getOrNull()
|
||||
?: break
|
||||
|
||||
nextIndex++
|
||||
}
|
||||
|
||||
accountsCRUDRepository.saveAccounts(accountList)
|
||||
}
|
||||
|
||||
toggleButtonProgress(title = title, isInProgress = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun archiveAllAccounts(title: String) {
|
||||
val userWalletId = getUserWallet()?.walletId ?: return
|
||||
val accountList = walletAccounts.value[userWalletId] ?: return
|
||||
|
||||
viewModelScope.launch {
|
||||
toggleButtonProgress(title = title, isInProgress = true)
|
||||
|
||||
withContext(dispatchers.default) {
|
||||
val updatedAccountList = AccountList.invoke(
|
||||
userWalletId = accountList.userWalletId,
|
||||
accounts = setOf(accountList.mainAccount),
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
)
|
||||
.getOrElse { return@withContext }
|
||||
|
||||
accountsCRUDRepository.saveAccounts(accountList = updatedAccountList)
|
||||
}
|
||||
|
||||
toggleButtonProgress(title = title, isInProgress = false)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun sortAccountsByIndex(title: String) {
|
||||
val userWalletId = getUserWallet()?.walletId ?: return
|
||||
val accountList = walletAccounts.value[userWalletId] ?: return
|
||||
|
||||
viewModelScope.launch {
|
||||
toggleButtonProgress(title = title, isInProgress = true)
|
||||
|
||||
withContext(dispatchers.default) {
|
||||
val sortedAccountList = AccountList.invoke(
|
||||
userWalletId = accountList.userWalletId,
|
||||
accounts = accountList.accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
.sortedBy { it.derivationIndex.value }
|
||||
.toSet(),
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
sortType = accountList.sortType,
|
||||
groupType = accountList.groupType,
|
||||
)
|
||||
.getOrElse { return@withContext }
|
||||
|
||||
accountsCRUDRepository.saveAccounts(accountList = sortedAccountList)
|
||||
}
|
||||
|
||||
toggleButtonProgress(title = title, isInProgress = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUserWallet(): UserWallet? = uiState.value.walletSelector.selected
|
||||
|
||||
private fun getWalletAccounts(userWalletId: UserWalletId): ImmutableList<Account.CryptoPortfolio> {
|
||||
return walletAccounts.value[userWalletId]?.accounts
|
||||
?.filterIsInstance<Account.CryptoPortfolio>()
|
||||
?.toImmutableList()
|
||||
?: persistentListOf()
|
||||
}
|
||||
|
||||
private fun toggleButtonProgress(title: String, isInProgress: Boolean) {
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
buttons = state.buttons.updateButton(title) {
|
||||
it.copy(isInProgress = isInProgress)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ImmutableList<AccountsUM.Button>.updateButton(
|
||||
title: String,
|
||||
button: (AccountsUM.Button) -> AccountsUM.Button,
|
||||
): ImmutableList<AccountsUM.Button> {
|
||||
return this
|
||||
.map { if (it.title == title) button(it) else it }
|
||||
.toImmutableList()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue