diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseHeaders.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseHeaders.kt index 52464501c9..024c982621 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseHeaders.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseHeaders.kt @@ -1,3 +1,3 @@ package com.tangem.datasource.api.common.response -const val IF_NONE_MATCH_HEADER = "IfNoneMatch" \ No newline at end of file +const val ETAG_HEADER = "etag" \ No newline at end of file diff --git a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt index cdd6967107..4fcfd53598 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt @@ -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) diff --git a/data/account/src/main/kotlin/com/tangem/data/account/store/AccountsResponseStoreFactory.kt b/data/account/src/main/kotlin/com/tangem/data/account/store/AccountsResponseStoreFactory.kt index a8ffea4d37..df3f05aaa0 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/store/AccountsResponseStoreFactory.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/store/AccountsResponseStoreFactory.kt @@ -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 @@ -30,6 +31,7 @@ typealias AccountsResponseStore = DataStore * [REDACTED_AUTHOR] */ +@Singleton internal class AccountsResponseStoreFactory @Inject constructor( @ApplicationContext private val context: Context, @NetworkMoshi private val moshi: Moshi, diff --git a/data/account/src/test/java/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcherTest.kt b/data/account/src/test/java/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcherTest.kt index d90aa7727c..d79de32016 100644 --- a/data/account/src/test/java/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcherTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcherTest.kt @@ -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( diff --git a/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/DefaultETagsStore.kt b/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/DefaultETagsStore.kt index 5c8a9b2dc1..90921f262e 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/DefaultETagsStore.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/DefaultETagsStore.kt @@ -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 { return stringPreferencesKey(name = "etag_${key}_${userWalletId.stringValue}") } diff --git a/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/ETagsStore.kt b/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/ETagsStore.kt index bae6820a73..a1cff7234f 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/ETagsStore.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/ETagsStore.kt @@ -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, diff --git a/features/tester/impl/build.gradle.kts b/features/tester/impl/build.gradle.kts index 6be7aca95c..57fe70672c 100644 --- a/features/tester/impl/build.gradle.kts +++ b/features/tester/impl/build.gradle.kts @@ -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) } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt index 8cd7fd94f1..1428366edc 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/TesterActivity.kt @@ -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().apply { + setupNavigation(innerTesterRouter) + } + val state by viewModel.uiState.collectAsStateWithLifecycle() + + AccountsScreen(state) + } } } } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/entity/AccountsUM.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/entity/AccountsUM.kt new file mode 100644 index 0000000000..168afd1186 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/entity/AccountsUM.kt @@ -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, + val onWalletSelect: (UserWallet) -> Unit, + ) : TangemBottomSheetConfigContent + + data class AccountListBottomSheetConfig( + val accounts: ImmutableList, + ) : TangemBottomSheetConfigContent +} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/ui/AccountsScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/ui/AccountsScreen.kt new file mode 100644 index 0000000000..66202d3842 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/ui/AccountsScreen.kt @@ -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(), + ) + } + } +} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/AccountsViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/AccountsViewModel.kt new file mode 100644 index 0000000000..31be606a60 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/AccountsViewModel.kt @@ -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 + field = MutableStateFlow(getInitialState()) + + private val walletAccounts: StateFlow> + 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() + ?.toImmutableList() + ?: persistentListOf(), + ), + ) + } + } + + private fun updateAccountsList() { + val userWalletId = uiState.value.walletSelector.selected?.walletId ?: return + + val accounts = walletAccounts.value[userWalletId]?.accounts + ?.filterIsInstance() + ?.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) + } + } +} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/state/TesterMenuUM.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/state/TesterMenuUM.kt index 5c27d1f688..fa30058309 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/state/TesterMenuUM.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/state/TesterMenuUM.kt @@ -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), } } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/navigation/TesterScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/navigation/TesterScreen.kt index 8b45b44ea4..e87d942c19 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/navigation/TesterScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/navigation/TesterScreen.kt @@ -13,4 +13,5 @@ internal enum class TesterScreen { EXCLUDED_BLOCKCHAINS, BLOCKCHAIN_PROVIDERS, TEST_PUSHES, + ACCOUNTS, } \ No newline at end of file diff --git a/features/tester/impl/src/main/res/values/strings.xml b/features/tester/impl/src/main/res/values/strings.xml index c7b4b4606c..c47c184c25 100644 --- a/features/tester/impl/src/main/res/values/strings.xml +++ b/features/tester/impl/src/main/res/values/strings.xml @@ -17,4 +17,5 @@ Blockchain providers Share logs Test push + Accounts