Updated on 2026-08-14
This commit is contained in:
parent
f75a69abfe
commit
4d7ae277a4
14 changed files with 498 additions and 10 deletions
|
|
@ -1,3 +1,3 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
const val IF_NONE_MATCH_HEADER = "IfNoneMatch"
|
||||
const val ETAG_HEADER = "etag"
|
||||
|
|
@ -11,7 +11,7 @@ import com.tangem.data.common.cache.etag.ETagsStore
|
|||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.Code
|
||||
import com.tangem.datasource.api.common.response.IF_NONE_MATCH_HEADER
|
||||
import com.tangem.datasource.api.common.response.ETAG_HEADER
|
||||
import com.tangem.datasource.api.common.response.isNetworkError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
|
|
@ -86,7 +86,11 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
|
|||
tangemTechApi.saveWalletAccounts(
|
||||
walletId = userWalletId.stringValue,
|
||||
eTag = eTag,
|
||||
body = body,
|
||||
body = body.copy(
|
||||
accounts = body.accounts.map {
|
||||
it.copy(tokens = null, totalTokens = null, totalNetworks = null)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +144,7 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
|
|||
private suspend fun assignTokens(userWalletId: UserWalletId, accountsResponse: GetWalletAccountsResponse) {
|
||||
val accountsResponseWithTokens = accountsResponse.assignTokens(userWalletId)
|
||||
|
||||
pushAndStore(userWalletId = userWalletId, response = accountsResponseWithTokens)
|
||||
store(userWalletId = userWalletId, response = accountsResponseWithTokens)
|
||||
|
||||
userTokensSaver.push(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -153,7 +157,7 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun saveETag(userWalletId: UserWalletId, apiResponse: ApiResponse<*>) {
|
||||
val eTag = apiResponse.headers[IF_NONE_MATCH_HEADER]?.firstOrNull()
|
||||
val eTag = apiResponse.headers[ETAG_HEADER]?.firstOrNull()
|
||||
|
||||
if (eTag != null) {
|
||||
eTagsStore.store(userWalletId = userWalletId, key = ETagsStore.Key.WalletAccounts, value = eTag)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.SupervisorJob
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
typealias AccountsResponseStore = DataStore<GetWalletAccountsResponse?>
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ typealias AccountsResponseStore = DataStore<GetWalletAccountsResponse?>
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Singleton
|
||||
internal class AccountsResponseStoreFactory @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
@NetworkMoshi private val moshi: Moshi,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.data.common.cache.etag.ETagsStore
|
|||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.IF_NONE_MATCH_HEADER
|
||||
import com.tangem.datasource.api.common.response.ETAG_HEADER
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
|
|
@ -85,7 +85,7 @@ class DefaultWalletAccountsFetcherTest {
|
|||
val newETag = "newEtag"
|
||||
val apiResponse = ApiResponse.Success(
|
||||
data = accountsResponse,
|
||||
headers = mapOf(IF_NONE_MATCH_HEADER to listOf(newETag)),
|
||||
headers = mapOf(ETAG_HEADER to listOf(newETag)),
|
||||
)
|
||||
|
||||
accountsResponseStoreFlow.value = savedAccountsResponse
|
||||
|
|
@ -156,7 +156,7 @@ class DefaultWalletAccountsFetcherTest {
|
|||
val newETag = "newEtag"
|
||||
val apiResponse = ApiResponse.Success(
|
||||
data = accountsResponse,
|
||||
headers = mapOf(IF_NONE_MATCH_HEADER to listOf(newETag)),
|
||||
headers = mapOf(ETAG_HEADER to listOf(newETag)),
|
||||
)
|
||||
|
||||
accountsResponseStoreFlow.value = savedAccountsResponse
|
||||
|
|
@ -247,7 +247,7 @@ class DefaultWalletAccountsFetcherTest {
|
|||
val newETag = "newEtag"
|
||||
val apiResponse = ApiResponse.Success(
|
||||
data = accountsResponse,
|
||||
headers = mapOf(IF_NONE_MATCH_HEADER to listOf(newETag)),
|
||||
headers = mapOf(ETAG_HEADER to listOf(newETag)),
|
||||
)
|
||||
|
||||
val apiError = ApiResponseError.HttpException(
|
||||
|
|
|
|||
|
|
@ -34,6 +34,11 @@ internal class DefaultETagsStore(
|
|||
appPreferencesStore.store(key = key, value = value)
|
||||
}
|
||||
|
||||
override suspend fun clear(userWalletId: UserWalletId, key: ETagsStore.Key) {
|
||||
val key = getAccountsETagKey(userWalletId = userWalletId, key = key)
|
||||
appPreferencesStore.editData { it.remove(key) }
|
||||
}
|
||||
|
||||
private fun getAccountsETagKey(userWalletId: UserWalletId, key: ETagsStore.Key): Preferences.Key<String> {
|
||||
return stringPreferencesKey(name = "etag_${key}_${userWalletId.stringValue}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@ interface ETagsStore {
|
|||
*/
|
||||
suspend fun store(userWalletId: UserWalletId, key: Key, value: String)
|
||||
|
||||
/**
|
||||
* Clears the stored ETag value for the specified wallet and key
|
||||
*
|
||||
* @param userWalletId identifier of the user wallet
|
||||
* @param key the key for which to get the ETag value
|
||||
*/
|
||||
suspend fun clear(userWalletId: UserWalletId, key: Key)
|
||||
|
||||
/** Enumeration of possible keys for storing ETag values */
|
||||
enum class Key {
|
||||
WalletAccounts,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ dependencies {
|
|||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.appTheme)
|
||||
implementation(projects.domain.appTheme.models)
|
||||
implementation(projects.domain.card)
|
||||
|
|
@ -40,6 +41,8 @@ dependencies {
|
|||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.wallets)
|
||||
|
||||
implementation(projects.data.common)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
|
|
@ -62,6 +65,7 @@ dependencies {
|
|||
|
||||
/** Other modules */
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@ import com.tangem.core.navigation.finisher.AppFinisher
|
|||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeActivity
|
||||
import com.tangem.feature.tester.presentation.accounts.ui.AccountsScreen
|
||||
import com.tangem.feature.tester.presentation.accounts.viewmodel.AccountsViewModel
|
||||
import com.tangem.feature.tester.presentation.actions.TesterActionsScreen
|
||||
import com.tangem.feature.tester.presentation.actions.TesterActionsViewModel
|
||||
import com.tangem.feature.tester.presentation.environments.ui.EnvironmentTogglesScreen
|
||||
|
|
@ -62,7 +64,7 @@ internal class TesterActivity : ComposeActivity() {
|
|||
TesterNavHost()
|
||||
}
|
||||
|
||||
@Suppress("TopLevelComposableFunctions", "LongMethod")
|
||||
@Suppress("TopLevelComposableFunctions", "LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
private fun TesterNavHost() {
|
||||
val navController = rememberNavController().also { innerTesterRouter.setNavController(it) }
|
||||
|
|
@ -79,6 +81,7 @@ internal class TesterActivity : ComposeActivity() {
|
|||
ButtonUM.BLOCKCHAIN_PROVIDERS,
|
||||
ButtonUM.TESTER_ACTIONS,
|
||||
ButtonUM.TEST_PUSHES,
|
||||
ButtonUM.ACCOUNTS,
|
||||
),
|
||||
onButtonClick = {
|
||||
val route = when (it) {
|
||||
|
|
@ -88,6 +91,7 @@ internal class TesterActivity : ComposeActivity() {
|
|||
ButtonUM.BLOCKCHAIN_PROVIDERS -> TesterScreen.BLOCKCHAIN_PROVIDERS
|
||||
ButtonUM.TESTER_ACTIONS -> TesterScreen.TESTER_ACTIONS
|
||||
ButtonUM.TEST_PUSHES -> TesterScreen.TEST_PUSHES
|
||||
ButtonUM.ACCOUNTS -> TesterScreen.ACCOUNTS
|
||||
}
|
||||
|
||||
innerTesterRouter.open(route)
|
||||
|
|
@ -149,6 +153,15 @@ internal class TesterActivity : ComposeActivity() {
|
|||
|
||||
TestPushScreen(state, viewModel)
|
||||
}
|
||||
|
||||
composable(route = TesterScreen.ACCOUNTS.name) {
|
||||
val viewModel = hiltViewModel<AccountsViewModel>().apply {
|
||||
setupNavigation(innerTesterRouter)
|
||||
}
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
AccountsScreen(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.feature.tester.presentation.accounts.entity
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class AccountsUM(
|
||||
val onBackClick: () -> Unit,
|
||||
val walletSelector: WalletSelector,
|
||||
val accountListBottomSheetConfig: AccountListBottomSheetConfig,
|
||||
val onAccountsClick: () -> Unit,
|
||||
val onFetchAccountsClick: () -> Unit,
|
||||
val onCreateMainAccountClick: () -> Unit,
|
||||
val onClearETagClick: () -> Unit,
|
||||
) {
|
||||
|
||||
data class WalletSelector(
|
||||
val selected: UserWallet?,
|
||||
val wallets: ImmutableList<UserWallet>,
|
||||
val onWalletSelect: (UserWallet) -> Unit,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
||||
data class AccountListBottomSheetConfig(
|
||||
val accounts: ImmutableList<Account.CryptoPortfolio>,
|
||||
) : TangemBottomSheetConfigContent
|
||||
}
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
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
|
||||
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.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
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.divider.DividerWithPadding
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun AccountsScreen(state: AccountsUM, modifier: Modifier = Modifier) {
|
||||
BackHandler(onBack = state.onBackClick)
|
||||
|
||||
var isWalletSelectorShown by remember { mutableStateOf(false) }
|
||||
var isAccountListShown by remember { mutableStateOf(false) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
stickyHeader { AppBar(onBackClick = state.onBackClick) }
|
||||
|
||||
item {
|
||||
WalletInfoBlock(
|
||||
walletName = state.walletSelector.selected?.name,
|
||||
onClick = {
|
||||
if (state.walletSelector.wallets.isNotEmpty()) {
|
||||
isWalletSelectorShown = true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (state.walletSelector.selected != null) {
|
||||
ManageAccountsButtons(
|
||||
state = state,
|
||||
onAccountsClick = { context ->
|
||||
if (state.accountListBottomSheetConfig.accounts.isNotEmpty()) {
|
||||
state.onAccountsClick()
|
||||
isAccountListShown = true
|
||||
} else {
|
||||
Toast.makeText(context, "No accounts found", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
WalletSelector(
|
||||
isShown = isWalletSelectorShown,
|
||||
onDismissRequest = { isWalletSelectorShown = false },
|
||||
content = state.walletSelector,
|
||||
)
|
||||
|
||||
AccountList(
|
||||
isShown = isAccountListShown,
|
||||
onDismissRequest = { isAccountListShown = false },
|
||||
content = state.accountListBottomSheetConfig,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(onBackClick: () -> Unit) {
|
||||
AppBarWithBackButton(
|
||||
onBackClick = onBackClick,
|
||||
text = stringResourceSafe(id = R.string.accounts),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletInfoBlock(walletName: String?, onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 48.dp)
|
||||
.background(color = TangemTheme.colors.background.secondary, shape = RoundedCornerShape(16.dp))
|
||||
.clickable(onClick = onClick)
|
||||
.padding(vertical = 12.dp, horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_wallet_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
|
||||
val text by remember(walletName) { mutableStateOf(walletName ?: "Choose wallet") }
|
||||
Text(
|
||||
text = text,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_chevron_right_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletSelector(isShown: Boolean, onDismissRequest: () -> Unit, content: AccountsUM.WalletSelector) {
|
||||
TangemBottomSheet(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = isShown,
|
||||
onDismissRequest = onDismissRequest,
|
||||
content = content,
|
||||
),
|
||||
titleText = stringReference("Choose wallet"),
|
||||
) { content: AccountsUM.WalletSelector ->
|
||||
content.wallets.fastForEachIndexed { index, wallet ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
content.onWalletSelect(wallet)
|
||||
onDismissRequest()
|
||||
}
|
||||
.padding(horizontal = 24.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = wallet.name,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
if (wallet.walletId == content.selected?.walletId) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_check_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (index == content.wallets.lastIndex) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
} else {
|
||||
DividerWithPadding(start = 16.dp, end = 16.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AccountList(
|
||||
isShown: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
content: AccountsUM.AccountListBottomSheetConfig,
|
||||
) {
|
||||
TangemBottomSheet(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = isShown,
|
||||
onDismissRequest = onDismissRequest,
|
||||
content = content,
|
||||
),
|
||||
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,
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
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,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
if (state.accountListBottomSheetConfig.accounts.none { it.isMainAccount }) {
|
||||
item {
|
||||
PrimaryButton(
|
||||
text = "Create Main account",
|
||||
onClick = state.onCreateMainAccountClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
package com.tangem.feature.tester.presentation.accounts.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
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.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltViewModel
|
||||
internal class AccountsViewModel @Inject constructor(
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val singleAccountListFetcher: SingleAccountListFetcher,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val eTagsStore: ETagsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ViewModel() {
|
||||
|
||||
val uiState: StateFlow<AccountsUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
private val walletAccounts: StateFlow<Map<UserWalletId, AccountList>>
|
||||
field = MutableStateFlow(emptyMap())
|
||||
|
||||
private var router: InnerTesterRouter? = null
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
walletSelector = state.walletSelector.copy(wallets = userWallets.toImmutableList()),
|
||||
)
|
||||
}
|
||||
|
||||
userWallets.mapIndexed { index, userWallet ->
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId = userWallet.walletId))
|
||||
.distinctUntilChanged()
|
||||
.onEach { accountList ->
|
||||
walletAccounts.update { currentMap ->
|
||||
currentMap.toMutableMap().apply {
|
||||
this[userWallet.walletId] = accountList
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setupNavigation(router: InnerTesterRouter) {
|
||||
this.router = router
|
||||
}
|
||||
|
||||
private fun getInitialState(): AccountsUM {
|
||||
return AccountsUM(
|
||||
onBackClick = { router?.back() },
|
||||
walletSelector = AccountsUM.WalletSelector(
|
||||
selected = null,
|
||||
wallets = persistentListOf(),
|
||||
onWalletSelect = ::onWalletSelect,
|
||||
),
|
||||
accountListBottomSheetConfig = AccountsUM.AccountListBottomSheetConfig(
|
||||
accounts = persistentListOf(),
|
||||
),
|
||||
onAccountsClick = ::updateAccountsList,
|
||||
onFetchAccountsClick = ::fetchAccounts,
|
||||
onCreateMainAccountClick = ::createMainAccount,
|
||||
onClearETagClick = ::clearETag,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onWalletSelect(userWallet: UserWallet) {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
walletSelector = it.walletSelector.copy(selected = userWallet),
|
||||
accountListBottomSheetConfig = it.accountListBottomSheetConfig.copy(
|
||||
accounts = walletAccounts.value[userWallet.walletId]?.accounts
|
||||
?.filterIsInstance<Account.CryptoPortfolio>()
|
||||
?.toImmutableList()
|
||||
?: persistentListOf(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAccountsList() {
|
||||
val userWalletId = uiState.value.walletSelector.selected?.walletId ?: return
|
||||
|
||||
val accounts = walletAccounts.value[userWalletId]?.accounts
|
||||
?.filterIsInstance<Account.CryptoPortfolio>()
|
||||
?.toImmutableList()
|
||||
?: persistentListOf()
|
||||
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
accountListBottomSheetConfig = state.accountListBottomSheetConfig.copy(
|
||||
accounts = accounts,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchAccounts() {
|
||||
viewModelScope.launch {
|
||||
val userWalletId = uiState.value.walletSelector.selected?.walletId ?: return@launch
|
||||
|
||||
singleAccountListFetcher(
|
||||
params = SingleAccountListFetcher.Params(userWalletId = userWalletId),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMainAccount() {
|
||||
viewModelScope.launch {
|
||||
val userWallet = uiState.value.walletSelector.selected ?: return@launch
|
||||
|
||||
// It's temporary solution to create main account for testing purposes
|
||||
val accountList = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(
|
||||
Account.CryptoPortfolio.createMainAccount(userWallet.walletId).copy(
|
||||
accountName = AccountName.invoke(value = "Main Account").getOrNull()!!,
|
||||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
||||
accountsCRUDRepository.saveAccounts(accountList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearETag() {
|
||||
viewModelScope.launch {
|
||||
val userWallet = uiState.value.walletSelector.selected ?: return@launch
|
||||
eTagsStore.clear(userWalletId = userWallet.walletId, key = ETagsStore.Key.WalletAccounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,5 +24,6 @@ data class TesterMenuUM(
|
|||
BLOCKCHAIN_PROVIDERS(R.string.blockchain_providers),
|
||||
TESTER_ACTIONS(R.string.tester_actions),
|
||||
TEST_PUSHES(R.string.test_push),
|
||||
ACCOUNTS(R.string.accounts),
|
||||
}
|
||||
}
|
||||
|
|
@ -13,4 +13,5 @@ internal enum class TesterScreen {
|
|||
EXCLUDED_BLOCKCHAINS,
|
||||
BLOCKCHAIN_PROVIDERS,
|
||||
TEST_PUSHES,
|
||||
ACCOUNTS,
|
||||
}
|
||||
|
|
@ -17,4 +17,5 @@
|
|||
<string name="blockchain_providers" translatable="false">Blockchain providers</string>
|
||||
<string name="share_logs" translatable="false">Share logs</string>
|
||||
<string name="test_push" translatable="false">Test push</string>
|
||||
<string name="accounts" translatable="false">Accounts</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue