From d58ac113f5efb9dd323adbe1a8171317f0975e15 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 Nov 2023 15:44:02 +0400 Subject: [PATCH 1/8] Updated on 2026-08-14 --- .../components/SettingsSwitchItem.kt | 2 +- .../bottomsheets/TangemBottomSheet.kt | 4 +- .../common/ui/ChooseWalletBottomSheet.kt | 22 +++ .../managetokens/state/ManageTokensState.kt | 24 +++ .../ManageTokensStatePreviewData.kt | 40 +++++ .../ui/ChooseNetworkBottomSheet.kt | 24 +++ .../managetokens/ui/ManageTokensScreen.kt | 144 ++++++++++++++++++ .../ui/components/AddCustomTokenButton.kt | 65 ++++++++ .../managetokens/ui/components/TokensList.kt | 59 +++++++ 9 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletBottomSheet.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkBottomSheet.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt index b584136754..e4198a521c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt @@ -14,11 +14,11 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.components.SpacerH4 import com.tangem.core.ui.components.SpacerW32 +import com.tangem.core.ui.components.TangemSwitch import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item -import com.tangem.core.ui.components.TangemSwitch @Composable internal fun SettingsSwitchItem(item: Item.Switch, modifier: Modifier = Modifier) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt index 2d9d85b164..76a172ffd9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt @@ -6,6 +6,7 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SheetState import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.* +import androidx.compose.ui.graphics.Color import com.tangem.core.ui.res.TangemTheme import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -20,6 +21,7 @@ import kotlinx.coroutines.launch @Composable inline fun TangemBottomSheet( config: TangemBottomSheetConfig, + contentColor: Color = TangemTheme.colors.background.primary, crossinline content: @Composable ColumnScope.(T) -> Unit, ) { var isVisible by remember { mutableStateOf(value = config.isShow) } @@ -29,7 +31,7 @@ inline fun TangemBottomSheet( ModalBottomSheet( onDismissRequest = config.onDismissRequest, sheetState = sheetState, - containerColor = TangemTheme.colors.background.primary, + containerColor = contentColor, shape = TangemTheme.shapes.bottomSheetLarge, dragHandle = { TangemBottomSheetDraggableHeader() }, ) { diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletBottomSheet.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletBottomSheet.kt new file mode 100644 index 0000000000..610f54edc4 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletBottomSheet.kt @@ -0,0 +1,22 @@ +package com.tangem.managetokens.presentation.common.ui + +import androidx.compose.runtime.Composable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.res.TangemTheme +import com.tangem.managetokens.presentation.common.state.ChooseWalletState + +@Composable +internal fun ChooseWalletBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + contentColor = TangemTheme.colors.background.tertiary, + ) { + ChooseWalletScreen(state = it.chooseWalletState) + } +} + +internal class ChooseWalletBottomSheetConfig( + val chooseWalletState: ChooseWalletState.Choose, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt new file mode 100644 index 0000000000..1ee0ecebce --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt @@ -0,0 +1,24 @@ +package com.tangem.managetokens.presentation.managetokens.state + +import androidx.paging.PagingData +import com.tangem.core.ui.event.StateEvent +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.common.state.Event +import kotlinx.coroutines.flow.Flow + +internal data class ManageTokensState( + val searchBarState: SearchBarState, + val tokens: Flow>, + val isLoading: Boolean, + val addCustomTokenButton: AddCustomTokenButton, + val chooseWalletState: ChooseWalletState, + val derivationNotification: DerivationNotificationState? = null, + val selectedToken: TokenItemState.Loaded? = null, + val showChooseWalletScreen: Boolean = false, + val event: StateEvent, +) + +data class AddCustomTokenButton( + val isVisible: Boolean, + val onClick: () -> Unit, +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt new file mode 100644 index 0000000000..6ea8316ba5 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt @@ -0,0 +1,40 @@ +package com.tangem.managetokens.presentation.managetokens.state.previewdata + +import androidx.paging.PagingData +import com.tangem.core.ui.event.consumedEvent +import com.tangem.managetokens.presentation.common.state.previewdata.ChooseWalletStatePreviewData +import com.tangem.managetokens.presentation.managetokens.state.AddCustomTokenButton +import com.tangem.managetokens.presentation.managetokens.state.ManageTokensState +import com.tangem.managetokens.presentation.managetokens.state.SearchBarState +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState +import kotlinx.coroutines.flow.flowOf + +internal object ManageTokensStatePreviewData { + val loadedState: ManageTokensState + get() = ManageTokensState( + searchBarState = searchState, + tokens = flowOf(PagingData.from(tokens)), + isLoading = false, + addCustomTokenButton = AddCustomTokenButton(true, {}), + derivationNotification = DerivationNotificationStatePreviewData.state, + event = consumedEvent(), + chooseWalletState = ChooseWalletStatePreviewData.state, + ) + + val loadingState: ManageTokensState + get() = loadedState.copy(isLoading = true) + + private val tokens: List + get() = listOf( + TokenItemStatePreviewData.loadedPriceDown, + TokenItemStatePreviewData.loadedPriceUp, + ) + + private val searchState: SearchBarState + get() = SearchBarState( + query = "", + onQueryChange = {}, + active = false, + onActiveChange = {}, + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkBottomSheet.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkBottomSheet.kt new file mode 100644 index 0000000000..cd1809a96b --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkBottomSheet.kt @@ -0,0 +1,24 @@ +package com.tangem.managetokens.presentation.managetokens.ui + +import androidx.compose.runtime.Composable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.res.TangemTheme +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState + +@Composable +internal fun ChooseNetworkBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + contentColor = TangemTheme.colors.background.tertiary, + ) { + ChooseNetworkScreen(state = it.selectedToken, walletState = it.chooseWalletState) + } +} + +internal class ChooseNetworkBottomSheetConfig( + val selectedToken: TokenItemState.Loaded, + val chooseWalletState: ChooseWalletState, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt new file mode 100644 index 0000000000..1bbf0c61a9 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt @@ -0,0 +1,144 @@ +package com.tangem.managetokens.presentation.managetokens.ui + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.Surface +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.paging.compose.collectAsLazyPagingItems +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.res.TangemTheme +import com.tangem.managetokens.presentation.common.state.AlertState +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.common.ui.ChooseWalletBottomSheet +import com.tangem.managetokens.presentation.common.ui.ChooseWalletBottomSheetConfig +import com.tangem.managetokens.presentation.common.ui.EventEffect +import com.tangem.managetokens.presentation.common.ui.components.Alert +import com.tangem.managetokens.presentation.managetokens.state.ManageTokensState +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState +import com.tangem.managetokens.presentation.managetokens.state.previewdata.ManageTokensStatePreviewData +import com.tangem.managetokens.presentation.managetokens.ui.components.DerivationNotification +import com.tangem.managetokens.presentation.managetokens.ui.components.TokensList +import com.tangem.managetokens.presentation.managetokens.ui.components.TokensSearchBar + +@Composable +internal fun ManageTokensScreen(state: ManageTokensState) { + var alertState by remember { mutableStateOf(value = null) } + + EventEffect( + event = state.event, + onAlertStateSet = { alertState = it }, + ) + alertState?.let { + Alert(state = it, onDismiss = { alertState = null }) + } + + Content(state) +} + +@Composable +private fun Content(state: ManageTokensState) { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = TangemTheme.colors.background.primary) + .padding(top = TangemTheme.dimens.spacing32), + ) { + Column { + val listState = rememberLazyListState() + val raiseSearchBar by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } + + val elevation by animateDpAsState( + targetValue = if (raiseSearchBar) { + TangemTheme.dimens.elevation8 + } else { + TangemTheme.dimens.elevation0 + }, + label = "top_bar_elevation", + ) + Surface( + elevation = elevation, + modifier = Modifier, + ) { + TokensSearchBar( + state = state.searchBarState, + modifier = Modifier + .background(color = TangemTheme.colors.background.primary) + .padding(horizontal = TangemTheme.dimens.spacing16, vertical = TangemTheme.dimens.spacing20), + ) + } + val tokens = state.tokens.collectAsLazyPagingItems() + TokensList(tokens = tokens, addCustomTokenButton = state.addCustomTokenButton) + } + state.derivationNotification?.let { + DerivationNotification( + config = it.config, + modifier = Modifier + .align(Alignment.BottomCenter), + ) + } + state.selectedToken?.let { selectedToken -> + ManageTokensBottomSheet(selectedToken = selectedToken, state = state) + } + } +} + +@Composable +private fun ManageTokensBottomSheet(selectedToken: TokenItemState.Loaded, state: ManageTokensState) { + if (state.showChooseWalletScreen && state.chooseWalletState is ChooseWalletState.Choose) { + val config = TangemBottomSheetConfig( + isShow = true, + content = ChooseWalletBottomSheetConfig(state.chooseWalletState), + onDismissRequest = state.chooseWalletState.onCloseChoosingWalletClick, + ) + ChooseWalletBottomSheet(config) + } else { + val config = TangemBottomSheetConfig( + isShow = true, + content = ChooseNetworkBottomSheetConfig( + selectedToken = selectedToken, + chooseWalletState = state.chooseWalletState, + ), + onDismissRequest = selectedToken.chooseNetworkState.onCloseChooseNetworkScreen, + ) + ChooseNetworkBottomSheet(config) + } +} + +@Preview +@Composable +private fun Preview_ManageTokensScreen_LightTheme( + @PreviewParameter(ManageTokensConfigProvider::class) + state: ManageTokensState, +) { + TangemTheme(isDark = false) { + ManageTokensScreen(state) + } +} + +@Preview +@Composable +private fun Preview_ManageTokensScreen_DarkTheme( + @PreviewParameter(ManageTokensConfigProvider::class) + state: ManageTokensState, +) { + TangemTheme(isDark = true) { + ManageTokensScreen(state) + } +} + +private class ManageTokensConfigProvider : CollectionPreviewParameterProvider( + collection = listOf( + ManageTokensStatePreviewData.loadingState, + ManageTokensStatePreviewData.loadedState, + ), +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt new file mode 100644 index 0000000000..195bb4d60c --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt @@ -0,0 +1,65 @@ +package com.tangem.managetokens.presentation.managetokens.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.SpacerW +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.managetokens.impl.R + +@Composable +internal fun AddCustomTokenButton(onButtonClick: () -> Unit, modifier: Modifier = Modifier) { + Row( + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .defaultMinSize(minHeight = TangemTheme.dimens.size68) + .fillMaxWidth() + .clickable { onButtonClick() } + .padding(horizontal = TangemTheme.dimens.spacing16), + ) { + Box( + modifier = Modifier + .size(TangemTheme.dimens.size36) + .background(color = TangemTheme.colors.button.secondary, shape = CircleShape), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_plus_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) + } + SpacerW(width = TangemTheme.dimens.spacing12) + Text( + text = stringResource(id = R.string.add_custom_token_title), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + } +} + +@Preview +@Composable +private fun AddCustomTokenButton_Preview_Light() { + TangemTheme(isDark = false) { + AddCustomTokenButton(onButtonClick = { }) + } +} + +@Preview +@Composable +private fun AddCustomTokenButton_Preview_Dark() { + TangemTheme(isDark = true) { + AddCustomTokenButton(onButtonClick = { }) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt new file mode 100644 index 0000000000..fe6c1c2bf7 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt @@ -0,0 +1,59 @@ +package com.tangem.managetokens.presentation.managetokens.ui.components + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.managetokens.state.AddCustomTokenButton +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState + +private const val PLACEHOLDER_ITEMS_COUNT = 50 + +@Composable +internal fun TokensList(tokens: LazyPagingItems, addCustomTokenButton: AddCustomTokenButton) { + LazyColumn { + item { + Text( + text = stringResource(id = R.string.manage_tokens_title), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), + ) + } + + if (tokens.loadState.refresh is LoadState.Loading) { + items(PLACEHOLDER_ITEMS_COUNT) { + TokenRowItem(state = TokenItemState.Loading(it.toString())) + } + } else { + val tokensList = tokens.itemSnapshotList + if (tokensList.isEmpty()) { + item { + Text( + text = stringResource(id = R.string.manage_tokens_nothing_found), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), + ) + } + } + + items(items = tokensList.items, key = TokenItemState::id) { token -> + TokenRowItem(state = token) + } + + if (addCustomTokenButton.isVisible) { + item { + AddCustomTokenButton(onButtonClick = { addCustomTokenButton.onClick }) + } + } + } + } +} \ No newline at end of file From 45193e4878a9d28321604f492c67f628414cd57b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 29 Nov 2023 20:20:08 +0300 Subject: [PATCH 2/8] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings-blockchain.xml | 1 - core/res/src/main/res/values-fr/strings-blockchain.xml | 1 - core/res/src/main/res/values-it/strings-blockchain.xml | 1 - core/res/src/main/res/values-zh-rTW/strings-blockchain.xml | 1 - 4 files changed, 4 deletions(-) diff --git a/core/res/src/main/res/values-de/strings-blockchain.xml b/core/res/src/main/res/values-de/strings-blockchain.xml index b6c425deeb..d438a29cd8 100644 --- a/core/res/src/main/res/values-de/strings-blockchain.xml +++ b/core/res/src/main/res/values-de/strings-blockchain.xml @@ -1,7 +1,6 @@ Erhalt der Gebühr fehlgeschlagen - Laden Sie %1$s+ %2$s auf um ein Konto zu erstellen Minimaler Betrag ist %s Restbestand zu klein Falsche Gebühr diff --git a/core/res/src/main/res/values-fr/strings-blockchain.xml b/core/res/src/main/res/values-fr/strings-blockchain.xml index 42c3c19b03..0f4d6dccec 100644 --- a/core/res/src/main/res/values-fr/strings-blockchain.xml +++ b/core/res/src/main/res/values-fr/strings-blockchain.xml @@ -1,7 +1,6 @@ Échec de réception des commissions - Pour créer un compte, téléchargez %1$s+ %2$s Le montant minimal est de %s Le reste est trop petit Commission non valide diff --git a/core/res/src/main/res/values-it/strings-blockchain.xml b/core/res/src/main/res/values-it/strings-blockchain.xml index 2d50632130..338c03f0da 100644 --- a/core/res/src/main/res/values-it/strings-blockchain.xml +++ b/core/res/src/main/res/values-it/strings-blockchain.xml @@ -1,7 +1,6 @@ Impossibile ottenere la commissione - Scarica %1$s+ %2$s per creare un account L\'importo minimo è di %s L\'importo residuo è molto basso Commissione non valida diff --git a/core/res/src/main/res/values-zh-rTW/strings-blockchain.xml b/core/res/src/main/res/values-zh-rTW/strings-blockchain.xml index 5aa30cef8f..607268ec3a 100644 --- a/core/res/src/main/res/values-zh-rTW/strings-blockchain.xml +++ b/core/res/src/main/res/values-zh-rTW/strings-blockchain.xml @@ -4,7 +4,6 @@ 遺留資產 獲取費用失敗 由於 Kaspa 的限制,只有%1$d UTXO 可以放入單次交易中。這意味著您只能發送%2$s或更少數量。您需要減少數量。 - 加載 %1$s+ %2$s 以創建帳戶 目標帳戶未激活。發送 %s 或更多以激活帳戶 最小數量是 %s 更動太小 From ed5d5b1b33b59f5a7282047c8aa271d2b3b10f4c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 30 Nov 2023 10:36:39 +0400 Subject: [PATCH 3/8] Updated on 2026-08-14 --- .../middlewares/TradeCryptoMiddleware.kt | 11 ++- .../chooseaddress/ChooseAddressBottomSheet.kt | 3 +- .../tokenreceive/AddressMappers.kt | 43 ++++++++++ .../bottomsheets/tokenreceive/AddressModel.kt | 5 +- .../tokenreceive/TokenReceiveBottomSheet.kt | 12 +-- .../TokenReceiveBottomSheetConfig.kt | 3 +- .../core/ui/extensions/BlockchainIcons.kt | 1 - .../data/tokens/utils/NetworkStatusFactory.kt | 24 +++++- .../domain/common/extensions/Blockchain.kt | 5 +- .../walletmanager/WalletManagersFacade.kt | 6 ++ .../domain/walletmanager/model/Address.kt | 11 +++ .../model/UpdateWalletManagerResult.kt | 8 +- .../utils/UpdateWalletManagerResultFactory.kt | 30 ++++--- .../tokens/model/CryptoCurrencyStatus.kt | 8 +- .../domain/tokens/model/NetworkAddress.kt | 30 ++++--- ...PrimaryCurrencyStatusUpdatesUseCaseTest.kt | 4 +- .../tangem/domain/tokens/mock/MockNetworks.kt | 16 +++- .../domain/tokens/mock/MockTokensStates.kt | 17 +++- .../state/factory/TokenDetailsStateFactory.kt | 27 ++---- .../viewmodels/TokenDetailsViewModel.kt | 46 +++++----- .../wallet/viewmodels/WalletViewModel.kt | 84 ++++++++----------- .../WalletCurrencyActionsClickIntents.kt | 58 ++++++------- gradle/dependencies.toml | 4 +- 23 files changed, 269 insertions(+), 187 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/AddressMappers.kt create mode 100644 domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/Address.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index c3092a3714..33a23f9750 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -14,6 +14,7 @@ import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkAddress import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.features.send.api.navigation.SendRouter import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -123,7 +124,10 @@ class TradeCryptoMiddleware { } private fun proceedNewBuyAction(state: () -> AppState?, action: TradeCryptoAction.New.Buy) { - val networkAddress = action.cryptoCurrencyStatus.value.networkAddress?.defaultAddress ?: return + val networkAddress = action.cryptoCurrencyStatus.value.networkAddress + ?.defaultAddress + ?.let(NetworkAddress.Address::value) + ?: return val status = action.cryptoCurrencyStatus val currency = status.currency @@ -206,7 +210,10 @@ class TradeCryptoMiddleware { } private fun proceedNewSellAction(action: TradeCryptoAction.New.Sell) { - val networkAddress = action.cryptoCurrencyStatus.value.networkAddress?.defaultAddress ?: return + val networkAddress = action.cryptoCurrencyStatus.value.networkAddress + ?.defaultAddress + ?.let(NetworkAddress.Address::value) + ?: return val currency = action.cryptoCurrencyStatus.currency store.state.globalState.exchangeManager.getUrl( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheet.kt index 4bec68b142..098950828e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheet.kt @@ -8,6 +8,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.SimpleSettingsRow import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme @Composable @@ -24,7 +25,7 @@ private fun ChooseAddressBottomSheetContent(content: ChooseAddressBottomSheetCon ) { content.addressModels.forEach { addressModel -> SimpleSettingsRow( - title = addressModel.type.name, + title = addressModel.displayName.resolveReference(), icon = R.drawable.ic_arrow_top_right_24, onItemsClick = { content.onClick(addressModel) }, ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/AddressMappers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/AddressMappers.kt new file mode 100644 index 0000000000..dfd23f44c2 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/AddressMappers.kt @@ -0,0 +1,43 @@ +package com.tangem.core.ui.components.bottomsheets.tokenreceive + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkAddress + +private const val DSC_ADDRESS_NAME = "DSC" +private const val DEL_ADDRESS_NAME = "Main" + +private const val LEGACY_ADDRESS_NAME = "Legacy" +private const val DEFAULT_ADDRESS_NAME = "Default" + +fun Set.mapToAddressModels(currency: CryptoCurrency): List = this + .sortedBy { it.type.ordinal } + .map { address -> + AddressModel( + displayName = currency.network.getAddressDisplayName(address.type), + value = address.value, + type = when (address.type) { + NetworkAddress.Address.Type.Primary -> AddressModel.Type.Default + NetworkAddress.Address.Type.Secondary -> AddressModel.Type.Legacy + }, + ) + } + +private fun Network.getAddressDisplayName(addressType: NetworkAddress.Address.Type): TextReference { + return when (id.value) { + "decimal", "decimal/test" -> { + when (addressType) { + NetworkAddress.Address.Type.Primary -> stringReference(value = DEL_ADDRESS_NAME) + NetworkAddress.Address.Type.Secondary -> stringReference(value = DSC_ADDRESS_NAME) + } + } + else -> { + when (addressType) { + NetworkAddress.Address.Type.Primary -> stringReference(value = DEFAULT_ADDRESS_NAME) + NetworkAddress.Address.Type.Secondary -> stringReference(value = LEGACY_ADDRESS_NAME) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/AddressModel.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/AddressModel.kt index 0314b21846..8571fdfa35 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/AddressModel.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/AddressModel.kt @@ -1,8 +1,11 @@ package com.tangem.core.ui.components.bottomsheets.tokenreceive +import com.tangem.core.ui.extensions.TextReference + data class AddressModel( + val displayName: TextReference, val value: String, - val type: Type = Type.Default, + val type: Type, ) { enum class Type { Legacy, Default diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt index 704418b5ec..1057a43847 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.* +import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -26,6 +26,7 @@ import com.tangem.core.ui.components.SecondaryButtonIconStart import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.rememberQrPainters +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.shareText import com.tangem.core.ui.res.TangemTheme @@ -181,13 +182,6 @@ private fun getName(content: TokenReceiveBottomSheetConfig, index: Int): String return if (content.addresses.size < 2) { content.name } else { - "${ - stringResource( - id = when (content.addresses[index].type) { - AddressModel.Type.Default -> R.string.address_type_default - AddressModel.Type.Legacy -> R.string.address_type_legacy - }, - ) - } ${content.name}" + "${content.addresses[index].displayName.resolveReference()} ${content.name}" } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheetConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheetConfig.kt index c2531a839d..a0a649302c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheetConfig.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheetConfig.kt @@ -1,12 +1,13 @@ package com.tangem.core.ui.components.bottomsheets.tokenreceive import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import kotlinx.collections.immutable.ImmutableList class TokenReceiveBottomSheetConfig( val name: String, val symbol: String, val network: String, - val addresses: List, + val addresses: ImmutableList, val onCopyClick: () -> Unit, val onShareClick: () -> Unit, ) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index e418da8ea3..95a8e5e94e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -28,7 +28,6 @@ fun getActiveIconRes(blockchainId: String): Int { "GNO" -> R.drawable.img_gnosis_22 "ETH-Pow", "ETH-Pow/test" -> R.drawable.img_eth_pow_22 "ETH-Fair" -> R.drawable.img_eth_fair_22 - "NEAR", "NEAR/test" -> R.drawable.img_near_22 "Polkadot", "Polkadot/test" -> R.drawable.img_polkadot_22 "Kusama" -> R.drawable.img_kusama_22 "OPTIMISM", "OPTIMISM/test" -> R.drawable.img_optimism_22 diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt index 47979ec3a4..dbbf3fb10a 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt @@ -2,6 +2,7 @@ package com.tangem.data.tokens.utils import com.tangem.domain.tokens.model.* import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.domain.walletmanager.model.Address import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult @@ -20,12 +21,12 @@ internal class NetworkStatusFactory { is UpdateWalletManagerResult.MissedDerivation -> NetworkStatus.MissedDerivation is UpdateWalletManagerResult.Unreachable -> NetworkStatus.Unreachable is UpdateWalletManagerResult.NoAccount -> NetworkStatus.NoAccount( - address = getNetworkAddress(result.defaultAddress, result.addresses), + address = getNetworkAddress(result.selectedAddress, result.addresses), amountToCreateAccount = result.amountToCreateAccount, errorMessage = result.errorMessage, ) is UpdateWalletManagerResult.Verified -> NetworkStatus.Verified( - address = getNetworkAddress(result.defaultAddress, result.addresses), + address = getNetworkAddress(result.selectedAddress, result.addresses), amounts = formatAmounts(result.currenciesAmounts, currencies), pendingTransactions = formatTransactions( transactions = result.currentTransactions, @@ -92,11 +93,26 @@ internal class NetworkStatusFactory { return transactions.mapTo(hashSetOf()) { it.txHistoryItem } } - private fun getNetworkAddress(defaultAddress: String, availableAddresses: Set): NetworkAddress { + private fun getNetworkAddress(selectedAddress: String, availableAddresses: Set
): NetworkAddress { + val defaultAddress = availableAddresses + .firstOrNull { it.value == selectedAddress } + ?.let(::mapToDomainAddress) + + requireNotNull(defaultAddress) { "Selected address must not be null" } + return if (availableAddresses.size != 1) { - NetworkAddress.Selectable(defaultAddress, availableAddresses) + NetworkAddress.Selectable(defaultAddress, availableAddresses.mapTo(hashSetOf(), ::mapToDomainAddress)) } else { NetworkAddress.Single(defaultAddress) } } + + private fun mapToDomainAddress(address: Address): NetworkAddress.Address { + val type = when (address.type) { + Address.Type.Primary -> NetworkAddress.Address.Type.Primary + Address.Type.Secondary -> NetworkAddress.Address.Type.Secondary + } + + return NetworkAddress.Address(address.value, type) + } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 2e72d3e67f..e4fb4f23a1 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -195,8 +195,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Chia, Blockchain.ChiaTestnet -> "chia" Blockchain.Near -> "near" Blockchain.NearTestnet -> "near/test" - Blockchain.Decimal -> "decimal" - Blockchain.DecimalTestnet -> "decimal/test" + Blockchain.Decimal, Blockchain.DecimalTestnet -> "decimal" Blockchain.Unknown -> "unknown" } } @@ -224,6 +223,4 @@ private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5 private val excludedBlockchains = listOf( Blockchain.Unknown, Blockchain.Ducatus, - Blockchain.Decimal, - Blockchain.DecimalTestnet, ) \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index f2b89b51a6..dba5d089eb 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -116,6 +116,7 @@ interface WalletManagersFacade { * @param userWalletId selected wallet id * @param network network of currency */ + @Deprecated("Use NetworkAddress from CryptoCurrencyStatus") suspend fun getAddress(userWalletId: UserWalletId, network: Network): List
/** Returns list of all addresses for all currencies in selected wallet @@ -123,6 +124,7 @@ interface WalletManagersFacade { * @param userWalletId selected wallet id * @param network required to create wallet manager */ + @Deprecated("Use NetworkAddress from CryptoCurrencyStatus") suspend fun getAddresses(userWalletId: UserWalletId, network: Network): Set
/** @@ -156,6 +158,7 @@ interface WalletManagersFacade { * @param userWalletId selected wallet id * @param network network of currency */ + @Deprecated("Will be removed in future") suspend fun getFee( amount: Amount, destination: String, @@ -171,6 +174,7 @@ interface WalletManagersFacade { * @param userWalletId selected wallet id * @param network network of currency */ + @Deprecated("Will be removed in future") suspend fun validateTransaction( amount: Amount, fee: Amount?, @@ -189,6 +193,7 @@ interface WalletManagersFacade { * @param network network of currency */ @Suppress("LongParameterList") + @Deprecated("Will be removed in future") suspend fun createTransaction( amount: Amount, fee: Fee, @@ -206,6 +211,7 @@ interface WalletManagersFacade { * @param userWalletId selected wallet id * @param network network of currency */ + @Deprecated("Will be removed in future") suspend fun sendTransaction( txData: TransactionData, signer: CommonSigner, diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/Address.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/Address.kt new file mode 100644 index 0000000000..c495bf7c9a --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/Address.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.walletmanager.model + +data class Address( + val value: String, + val type: Type, +) { + + enum class Type { + Primary, Secondary, + } +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/UpdateWalletManagerResult.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/UpdateWalletManagerResult.kt index ae14d752e1..dd1891105e 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/UpdateWalletManagerResult.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/UpdateWalletManagerResult.kt @@ -9,15 +9,15 @@ sealed class UpdateWalletManagerResult { object Unreachable : UpdateWalletManagerResult() data class Verified( - val defaultAddress: String, - val addresses: Set, + val selectedAddress: String, + val addresses: Set
, val currenciesAmounts: Set, val currentTransactions: Set, ) : UpdateWalletManagerResult() data class NoAccount( - val defaultAddress: String, - val addresses: Set, + val selectedAddress: String, + val addresses: Set
, val amountToCreateAccount: BigDecimal, val errorMessage: String, ) : UpdateWalletManagerResult() diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt index 1e9c032f6c..4a67053404 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/UpdateWalletManagerResultFactory.kt @@ -1,15 +1,17 @@ package com.tangem.domain.walletmanager.utils import com.tangem.blockchain.common.* -import com.tangem.blockchain.common.address.Address +import com.tangem.blockchain.common.address.AddressType import com.tangem.domain.common.extensions.amountToCreateAccount import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.domain.walletmanager.model.Address import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult import timber.log.Timber import java.math.BigDecimal import java.util.concurrent.TimeUnit +import com.tangem.blockchain.common.address.Address as SdkAddress internal class UpdateWalletManagerResultFactory { @@ -18,7 +20,7 @@ internal class UpdateWalletManagerResultFactory { val addresses = getAvailableAddresses(wallet.addresses) return UpdateWalletManagerResult.Verified( - defaultAddress = wallet.address, + selectedAddress = wallet.address, addresses = addresses, currenciesAmounts = getTokensAmounts(wallet.amounts.values.toSet()), currentTransactions = getCurrentTransactions(addresses, wallet.recentTransactions.toSet()), @@ -30,7 +32,7 @@ internal class UpdateWalletManagerResultFactory { val addresses = getAvailableAddresses(wallet.addresses) return UpdateWalletManagerResult.Verified( - defaultAddress = wallet.address, + selectedAddress = wallet.address, addresses = addresses, currenciesAmounts = getDemoTokensAmounts(demoAmount, walletManager.cardTokens), currentTransactions = getCurrentTransactions(addresses, wallet.recentTransactions.toSet()), @@ -48,7 +50,7 @@ internal class UpdateWalletManagerResultFactory { UpdateWalletManagerResult.Unreachable } else { UpdateWalletManagerResult.NoAccount( - defaultAddress = wallet.address, + selectedAddress = wallet.address, addresses = getAvailableAddresses(wallet.addresses), amountToCreateAccount = amountToCreateAccount, errorMessage = customMessage, @@ -70,7 +72,7 @@ internal class UpdateWalletManagerResultFactory { } private fun getCurrentTransactions( - walletAddresses: Set, + walletAddresses: Set
, recentTransactions: Set, ): Set { val unconfirmedTransactions = recentTransactions.filter { @@ -95,7 +97,7 @@ internal class UpdateWalletManagerResultFactory { } private fun createCurrencyTransaction( - walletAddresses: Set, + walletAddresses: Set
, data: TransactionData, ): CryptoCurrencyTransaction? { return when (val type = data.amount.type) { @@ -115,11 +117,11 @@ internal class UpdateWalletManagerResultFactory { } } - private fun createTxHistoryItem(walletAddresses: Set, data: TransactionData): TxHistoryItem? { + private fun createTxHistoryItem(walletAddresses: Set
, data: TransactionData): TxHistoryItem? { val hash = data.hash ?: return null val millis = data.date?.timeInMillis ?: return null val amount = getTransactionAmountValue(data.amount) ?: return null - val isOutgoing = data.sourceAddress in walletAddresses + val isOutgoing = data.sourceAddress in walletAddresses.map { it.value } return TxHistoryItem( txHash = hash, @@ -141,8 +143,16 @@ internal class UpdateWalletManagerResultFactory { ) } - private fun getAvailableAddresses(addresses: Set
): Set { - return addresses.mapTo(hashSetOf()) { it.value } + private fun getAvailableAddresses(addresses: Set): Set
{ + return addresses.mapTo(hashSetOf()) { sdkAddress -> + Address( + value = sdkAddress.value, + type = when (sdkAddress.type) { + AddressType.Default -> Address.Type.Primary + AddressType.Legacy -> Address.Type.Secondary + }, + ) + } } private fun getCurrencyAmountValue(amount: Amount): BigDecimal? { diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt index 5ba1af68eb..23700e3d7b 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt @@ -78,7 +78,7 @@ data class CryptoCurrencyStatus( override val fiatAmount: BigDecimal?, override val priceChange: BigDecimal?, override val fiatRate: BigDecimal?, - override val networkAddress: NetworkAddress?, + override val networkAddress: NetworkAddress, ) : Status(isError = false) { override val amount: BigDecimal = BigDecimal.ZERO @@ -102,7 +102,7 @@ data class CryptoCurrencyStatus( override val priceChange: BigDecimal, override val hasCurrentNetworkTransactions: Boolean, override val pendingTransactions: Set, - override val networkAddress: NetworkAddress?, + override val networkAddress: NetworkAddress, ) : Status(isError = false) /** @@ -123,7 +123,7 @@ data class CryptoCurrencyStatus( override val priceChange: BigDecimal?, override val hasCurrentNetworkTransactions: Boolean, override val pendingTransactions: Set, - override val networkAddress: NetworkAddress?, + override val networkAddress: NetworkAddress, ) : Status(isError = false) /** @@ -138,6 +138,6 @@ data class CryptoCurrencyStatus( override val amount: BigDecimal, override val hasCurrentNetworkTransactions: Boolean, override val pendingTransactions: Set, - override val networkAddress: NetworkAddress?, + override val networkAddress: NetworkAddress, ) : Status(isError = false) } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkAddress.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkAddress.kt index 542d48485e..70d50581d4 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkAddress.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkAddress.kt @@ -6,18 +6,19 @@ package com.tangem.domain.tokens.model sealed class NetworkAddress { /** The default or currently selected network address. */ - abstract val defaultAddress: String + abstract val defaultAddress: Address + + /** The set of available network addresses to choose from. */ + abstract val availableAddresses: Set
/** * Represents a single static network address. * * @property defaultAddress The static network address. */ - data class Single(override val defaultAddress: String) : NetworkAddress() { + data class Single(override val defaultAddress: Address) : NetworkAddress() { - init { - checkDefaultAddress() - } + override val availableAddresses: Set
= setOf(defaultAddress) } /** @@ -27,17 +28,26 @@ sealed class NetworkAddress { * @property availableAddresses The set of available network addresses to choose from. */ data class Selectable( - override val defaultAddress: String, - val availableAddresses: Set, + override val defaultAddress: Address, + override val availableAddresses: Set
, ) : NetworkAddress() { init { - checkDefaultAddress() require(availableAddresses.isNotEmpty()) { "Available network addresses must not be empty" } } } - protected fun checkDefaultAddress() { - require(defaultAddress.isNotBlank()) { "Selected network address must not be blank" } + data class Address( + val value: String, + val type: Type, + ) { + + enum class Type { + Primary, Secondary, + } + + init { + require(value.isNotBlank()) { "Address value must not be blank" } + } } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt index 6cdc76877c..b5c027ac68 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt @@ -118,7 +118,9 @@ internal class GetPrimaryCurrencyStatusUpdatesUseCaseTest { amount = BigDecimal.TEN, hasCurrentNetworkTransactions = false, pendingTransactions = emptySet(), - networkAddress = NetworkAddress.Single(defaultAddress = "mock"), + networkAddress = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), + ), ), ) } diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt index 979479e010..369bb8b5b3 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt @@ -51,7 +51,9 @@ internal object MockNetworks { network = network3, value = NetworkStatus.NoAccount( amountToCreateAccount = amountToCreateAccount, - address = NetworkAddress.Single(defaultAddress = "mock"), + address = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), + ), errorMessage = "", ), ) @@ -67,7 +69,9 @@ internal object MockNetworks { MockTokens.token3.id to CryptoCurrencyAmountStatus.Loaded(BigDecimal.TEN), ), pendingTransactions = emptyMap(), - address = NetworkAddress.Single(defaultAddress = "mock"), + address = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), + ), ), ) @@ -80,7 +84,9 @@ internal object MockNetworks { MockTokens.token6.id to CryptoCurrencyAmountStatus.Loaded(BigDecimal.TEN), ), pendingTransactions = emptyMap(), - address = NetworkAddress.Single(defaultAddress = "mock"), + address = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), + ), ), ) @@ -94,7 +100,9 @@ internal object MockNetworks { MockTokens.token10.id to CryptoCurrencyAmountStatus.Loaded(BigDecimal.TEN), ), pendingTransactions = emptyMap(), - address = NetworkAddress.Single(defaultAddress = "mock"), + address = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), + ), ), ) diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt index 38338ba598..bb9071f999 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt @@ -3,6 +3,7 @@ package com.tangem.domain.tokens.mock import arrow.core.nonEmptyListOf import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.NetworkAddress import com.tangem.domain.tokens.model.NetworkStatus import java.math.BigDecimal @@ -64,7 +65,9 @@ internal object MockTokensStates { priceChange = MockQuotes.quote7.priceChange, fiatRate = MockQuotes.quote7.fiatRate, amountToCreateAccount = MockNetworks.amountToCreateAccount, - networkAddress = null, + networkAddress = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), + ), ), ) @@ -75,7 +78,9 @@ internal object MockTokensStates { priceChange = MockQuotes.quote8.priceChange, fiatRate = MockQuotes.quote8.fiatRate, amountToCreateAccount = MockNetworks.amountToCreateAccount, - networkAddress = null, + networkAddress = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), + ), ), ) @@ -86,7 +91,9 @@ internal object MockTokensStates { priceChange = MockQuotes.quote9.priceChange, fiatRate = MockQuotes.quote9.fiatRate, amountToCreateAccount = MockNetworks.amountToCreateAccount, - networkAddress = null, + networkAddress = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), + ), ), ) @@ -97,7 +104,9 @@ internal object MockTokensStates { priceChange = MockQuotes.quote10.priceChange, fiatRate = MockQuotes.quote10.fiatRate, amountToCreateAccount = MockNetworks.amountToCreateAccount, - networkAddress = null, + networkAddress = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), + ), ), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 1494ed003f..997a6e985a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -2,12 +2,11 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory import androidx.paging.PagingData import arrow.core.Either -import com.tangem.blockchain.common.address.Address import com.tangem.common.Provider import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.event.consumedEvent @@ -17,6 +16,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.NetworkAddress import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.txhistory.models.TxHistoryItem @@ -174,7 +174,7 @@ internal class TokenDetailsStateFactory( fun getStateWithReceiveBottomSheet( currency: CryptoCurrency, - addresses: List
, + networkAddress: NetworkAddress, sendCopyAnalyticsEvent: () -> Unit, sendShareAnalyticsEvent: () -> Unit, ): TokenDetailsState { @@ -186,12 +186,7 @@ internal class TokenDetailsStateFactory( name = currency.name, symbol = currency.symbol, network = currency.network.name, - addresses = addresses.map { - AddressModel( - value = it.value, - type = AddressModel.Type.valueOf(it.type.name), - ) - }, + addresses = networkAddress.availableAddresses.mapToAddressModels(currency).toImmutableList(), onCopyClick = sendCopyAnalyticsEvent, onShareClick = sendShareAnalyticsEvent, ), @@ -199,20 +194,16 @@ internal class TokenDetailsStateFactory( ) } - fun getStateWithChooseAddressBottomSheet(addresses: List
): TokenDetailsState { + fun getStateWithChooseAddressBottomSheet( + currency: CryptoCurrency, + networkAddress: NetworkAddress, + ): TokenDetailsState { return currentStateProvider().copy( bottomSheetConfig = TangemBottomSheetConfig( isShow = true, onDismissRequest = clickIntents::onDismissBottomSheet, content = ChooseAddressBottomSheetConfig( - addressModels = addresses - .map { address -> - AddressModel( - value = address.value, - type = AddressModel.Type.valueOf(address.type.name), - ) - } - .toImmutableList(), + addressModels = networkAddress.availableAddresses.mapToAddressModels(currency).toImmutableList(), onClick = clickIntents::onAddressTypeSelected, ), ), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index b1496b2e0c..7be83bff9e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -23,12 +23,12 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.NetworkAddress import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase 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.UserWalletId import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase @@ -67,7 +67,6 @@ internal class TokenDetailsViewModel @Inject constructor( private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, - private val walletManagersFacade: WalletManagersFacade, private val isDemoCardUseCase: IsDemoCardUseCase, private val reduxStateHolder: ReduxStateHolder, private val analyticsEventsHandler: AnalyticsEventHandler, @@ -303,19 +302,15 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onReceiveClick() { - analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrency.symbol)) + val networkAddress = cryptoCurrencyStatus?.value?.networkAddress ?: return viewModelScope.launch(dispatchers.io) { - val addresses = walletManagersFacade.getAddress( - userWalletId = userWalletId, - network = cryptoCurrency.network, - ) - - analyticsEventsHandler.send(event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened) + analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrency.symbol)) + analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ReceiveScreenOpened) uiState = stateFactory.getStateWithReceiveBottomSheet( currency = cryptoCurrency, - addresses = addresses, + networkAddress = networkAddress, sendCopyAnalyticsEvent = { analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(cryptoCurrency.symbol)) }, @@ -378,22 +373,23 @@ internal class TokenDetailsViewModel @Inject constructor( } private fun openExplorer() { - viewModelScope.launch(dispatchers.io) { - val addresses = walletManagersFacade.getAddress( - userWalletId = userWalletId, - network = cryptoCurrency.network, - ) + val currencyStatus = cryptoCurrencyStatus ?: return - if (addresses.size == 1) { - router.openUrl( - url = getExploreUrlUseCase( - userWalletId = userWalletId, - currency = cryptoCurrency, - addressType = AddressType.Default, - ), - ) - } else { - uiState = stateFactory.getStateWithChooseAddressBottomSheet(addresses = addresses) + viewModelScope.launch(dispatchers.io) { + when (val addresses = currencyStatus.value.networkAddress) { + is NetworkAddress.Selectable -> { + uiState = stateFactory.getStateWithChooseAddressBottomSheet(cryptoCurrency, addresses) + } + is NetworkAddress.Single -> { + router.openUrl( + url = getExploreUrlUseCase( + userWalletId = userWalletId, + currency = cryptoCurrency, + addressType = AddressType.Default, + ), + ) + } + null -> Unit } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 1cb74caecf..6534df2c8c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -19,6 +19,7 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.WrappedList @@ -42,10 +43,7 @@ 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 -import com.tangem.domain.tokens.model.NetworkGroup -import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase @@ -689,20 +687,11 @@ internal class WalletViewModel @Inject constructor( } override fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { - val state = uiState as? WalletState.ContentState ?: return - analyticsEventsHandler.send( event = TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrencyStatus.currency.symbol), ) viewModelScope.launch(dispatchers.io) { - val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex) - - val addresses = walletManagersFacade.getAddress( - userWalletId = userWallet.walletId, - network = cryptoCurrencyStatus.currency.network, - ) - analyticsEventsHandler.send(event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened) val currency = cryptoCurrencyStatus.currency @@ -711,12 +700,11 @@ internal class WalletViewModel @Inject constructor( name = currency.name, symbol = currency.symbol, network = currency.network.name, - addresses = addresses.map { - AddressModel( - value = it.value, - type = AddressModel.Type.valueOf(it.type.name), - ) - }, + addresses = cryptoCurrencyStatus.value.networkAddress + ?.availableAddresses + ?.mapToAddressModels(currency) + .orEmpty() + .toImmutableList(), onCopyClick = { analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(currency.symbol)) }, @@ -821,41 +809,39 @@ internal class WalletViewModel @Inject constructor( private fun openExplorer() { val state = uiState as? WalletState.ContentState ?: return - val currency = singleWalletCryptoCurrencyStatus?.currency ?: return + val currencyStatus = singleWalletCryptoCurrencyStatus ?: return + val currency = currencyStatus.currency viewModelScope.launch(dispatchers.main) { val userWalletId = getWallet(state.walletsListConfig.selectedWalletIndex).walletId - val addresses = walletManagersFacade.getAddress(userWalletId = userWalletId, network = currency.network) - - if (addresses.size == 1) { - router.openUrl( - url = getExploreUrlUseCase( - userWalletId = userWalletId, - currency = currency, - addressType = AddressType.Default, - ), - ) - } else { - uiState = stateFactory.getStateWithOpenWalletBottomSheet( - ChooseAddressBottomSheetConfig( - addressModels = addresses - .map { address -> - AddressModel( - value = address.value, - type = AddressModel.Type.valueOf(address.type.name), + when (val addresses = currencyStatus.value.networkAddress) { + is NetworkAddress.Selectable -> { + uiState = stateFactory.getStateWithOpenWalletBottomSheet( + ChooseAddressBottomSheetConfig( + addressModels = addresses.availableAddresses + .mapToAddressModels(currency) + .toImmutableList(), + onClick = { + onAddressTypeSelected( + userWalletId = userWalletId, + currency = currency, + addressModel = it, ) - } - .toImmutableList(), - onClick = { - onAddressTypeSelected( - userWalletId = userWalletId, - currency = currency, - addressModel = it, - ) - }, - ), - ) + }, + ), + ) + } + is NetworkAddress.Single -> { + router.openUrl( + url = getExploreUrlUseCase( + userWalletId = userWalletId, + currency = currency, + addressType = AddressType.Default, + ), + ) + } + null -> Unit } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 9690fa7684..789557c99e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -1,12 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents -import com.tangem.blockchain.common.address.Address import com.tangem.blockchain.common.address.AddressType import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels import com.tangem.core.ui.extensions.WrappedList import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase @@ -20,6 +20,7 @@ import com.tangem.domain.tokens.RemoveCurrencyUseCase import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.NetworkAddress import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.walletconnect.WalletConnectActions @@ -141,15 +142,15 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( ) viewModelScope.launch(dispatchers.main) { - val currency = cryptoCurrencyStatus.currency - val addresses = walletManagersFacade.getAddress(userWalletId = userWalletId, network = currency.network) - analyticsEventHandler.send(event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened) stateHolder.update( OpenBottomSheetTransformer( userWalletId = userWalletId, - content = createReceiveBottomSheetContent(currency, addresses), + content = createReceiveBottomSheetContent( + currency = cryptoCurrencyStatus.currency, + addresses = cryptoCurrencyStatus.value.networkAddress?.availableAddresses ?: return@launch, + ), onDismissBottomSheet = { stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId)) }, @@ -160,18 +161,13 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private fun createReceiveBottomSheetContent( currency: CryptoCurrency, - addresses: List
, + addresses: Set, ): TangemBottomSheetConfigContent { return TokenReceiveBottomSheetConfig( name = currency.name, symbol = currency.symbol, network = currency.network.name, - addresses = addresses.map { address -> - AddressModel( - value = address.value, - type = AddressModel.Type.valueOf(address.type.name), - ) - }, + addresses = addresses.mapToAddressModels(currency).toImmutableList(), onCopyClick = { analyticsEventHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(currency.symbol)) }, @@ -329,40 +325,36 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( val userWalletId = stateHolder.getSelectedWalletId() viewModelScope.launch(dispatchers.main) { - val currency = getPrimaryCurrencyStatusUpdatesUseCase.unwrap(userWalletId)?.currency ?: return@launch - val addresses = walletManagersFacade.getAddress(userWalletId = userWalletId, network = currency.network) + val currencyStatus = getPrimaryCurrencyStatusUpdatesUseCase.unwrap(userWalletId) ?: return@launch - if (addresses.size == 1) { - router.openUrl( - url = getExploreUrlUseCase( - userWalletId = userWalletId, - currency = currency, - addressType = AddressType.Default, - ), - ) - } else { - showChooseAddressBottomSheet(userWalletId, addresses, currency) + when (val addresses = currencyStatus.value.networkAddress) { + is NetworkAddress.Selectable -> { + showChooseAddressBottomSheet(userWalletId, addresses.availableAddresses, currencyStatus.currency) + } + is NetworkAddress.Single -> { + router.openUrl( + url = getExploreUrlUseCase( + userWalletId = userWalletId, + currency = currencyStatus.currency, + addressType = AddressType.Default, + ), + ) + } + null -> Unit } } } private fun showChooseAddressBottomSheet( userWalletId: UserWalletId, - addresses: List
, + addresses: Set, currency: CryptoCurrency, ) { stateHolder.update( OpenBottomSheetTransformer( userWalletId = userWalletId, content = ChooseAddressBottomSheetConfig( - addressModels = addresses - .map { address -> - AddressModel( - value = address.value, - type = AddressModel.Type.valueOf(address.type.name), - ) - } - .toImmutableList(), + addressModels = addresses.mapToAddressModels(currency).toImmutableList(), onClick = { onAddressTypeSelected( userWalletId = userWalletId, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 95e9a29cf5..fb9f8dad5f 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,10 +88,10 @@ spr-client = "3.6.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-396" +tangemBlockchainSdk = "develop-401" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-312" -#tangemCardSdk = "0.0.1" # Keep it! - used for local builds +#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem # region Tools From 97f9283cea52862be282044136c9040c7ab82716 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 30 Nov 2023 12:17:43 +0300 Subject: [PATCH 4/8] Updated on 2026-08-14 --- .../presentation/router/DefaultWalletRouter.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 5dd19e4a33..e5ad37394c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -54,17 +54,17 @@ internal class DefaultWalletRouter( ) { composable(WalletRoute.Wallet.route) { if (walletFeatureToggles.isWalletsScrollingPreviewEnabled) { - val viewModel = hiltViewModel().apply { - router = this@DefaultWalletRouter - } - - WalletScreen(state = viewModel.uiState) - } else { val viewModel = hiltViewModel().apply { setWalletRouter(router = this@DefaultWalletRouter) } WalletScreenV2(state = viewModel.uiState.collectAsStateWithLifecycle().value) + } else { + val viewModel = hiltViewModel().apply { + router = this@DefaultWalletRouter + } + + WalletScreen(state = viewModel.uiState) } } From 44494e9ba6f0942609c8740542ac66a2f4b4b9d9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Nov 2023 16:43:57 +0100 Subject: [PATCH 5/8] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 11 ++- .../core/ui/extensions/CryptoCurrency.kt | 10 +++ data/tokens/build.gradle.kts | 1 + .../tangem/data/tokens/di/TokensDataModule.kt | 24 ++++-- .../data/tokens/paging/CoinsPagingSource.kt | 77 +++++++++++++++++++ .../tokens/paging/CoinsResponseConverter.kt | 51 ++++++++++++ .../repository/DefaultTokensListRepository.kt | 45 +++++++++++ .../data/tokens/utils/NetworkOperations.kt | 2 +- domain/tokens/build.gradle.kts | 3 + .../com/tangem/domain/tokens/model/Quote.kt | 7 +- .../com/tangem/domain/tokens/model/Token.kt | 41 ++++++++++ .../tokens/GetGlobalTokenListUseCase.kt | 22 ++++++ .../tokens/repository/TokensListRepository.kt | 20 +++++ features/manage-tokens/impl/build.gradle.kts | 3 + 14 files changed, 302 insertions(+), 15 deletions(-) create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsPagingSource.kt create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsResponseConverter.kt create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensListRepository.kt create mode 100644 domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Token.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetGlobalTokenListUseCase.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensListRepository.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index d66c81c65f..d4488fe919 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -2,10 +2,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.* -import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository -import com.tangem.domain.tokens.repository.NetworksRepository -import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.tokens.repository.* import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -231,4 +228,10 @@ internal object TokensDomainModule { ): GetMissedAddressesCryptoCurrenciesUseCase { return GetMissedAddressesCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository) } + + @Provides + @ViewModelScoped + fun provideGetGlobalTokenListUseCase(tokensListRepository: TokensListRepository): GetGlobalTokenListUseCase { + return GetGlobalTokenListUseCase(repository = tokensListRepository) + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/CryptoCurrency.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/CryptoCurrency.kt index cf0b07bbfe..277d3f4a52 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/CryptoCurrency.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/CryptoCurrency.kt @@ -32,6 +32,16 @@ fun CryptoCurrency.Token.tryGetBackgroundForTokenIcon( ): Color { if (isGrayscale) return TangemColorPalette.Dark2 + return tryGetBackgroundForTokenIcon(contractAddress = contractAddress, fallbackColor = fallbackColor) +} + +/** + * Tries to extract a background color from the contract address of a token. + * + * @param fallbackColor The color to use as a fallback. + * @return The extracted background color or the fallback color if extraction fails or if it is a test network token. + */ +fun tryGetBackgroundForTokenIcon(contractAddress: String, fallbackColor: Color = TangemColorPalette.Black): Color { return try { val colorHex = "#" + contractAddress.substring(range = COLOR_HEX_START_INDEX..COLOR_HEX_END_INDEX) Color(colorHex.toColorInt()) diff --git a/data/tokens/build.gradle.kts b/data/tokens/build.gradle.kts index 88d7471830..f183caa14f 100644 --- a/data/tokens/build.gradle.kts +++ b/data/tokens/build.gradle.kts @@ -45,4 +45,5 @@ dependencies { implementation(deps.jodatime) implementation(deps.timber) implementation(deps.retrofit) // For HttpException + implementation(deps.androidx.paging.runtime) } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 80ceefbdc9..f435378502 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -1,10 +1,7 @@ package com.tangem.data.tokens.di import com.tangem.data.common.cache.CacheRegistry -import com.tangem.data.tokens.repository.DefaultCurrenciesRepository -import com.tangem.data.tokens.repository.DefaultMarketCryptoCurrencyRepository -import com.tangem.data.tokens.repository.DefaultNetworksRepository -import com.tangem.data.tokens.repository.DefaultQuotesRepository +import com.tangem.data.tokens.repository.* import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.network.NetworksStatusesStore import com.tangem.datasource.local.preferences.AppPreferencesStore @@ -12,10 +9,7 @@ import com.tangem.datasource.local.quote.QuotesStore import com.tangem.datasource.local.token.UserMarketCoinsStore import com.tangem.datasource.local.token.UserTokensStore import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository -import com.tangem.domain.tokens.repository.NetworksRepository -import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.tokens.repository.* import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -93,4 +87,18 @@ internal object TokensDataModule { ): MarketCryptoCurrencyRepository { return DefaultMarketCryptoCurrencyRepository(userMarketCoinsStore) } + + @Provides + @Singleton + fun providesTokensListRepository( + tangemTechApi: TangemTechApi, + dispatchers: CoroutineDispatcherProvider, + quotesRepository: QuotesRepository, + ): TokensListRepository { + return DefaultTokensListRepository( + tangemTechApi = tangemTechApi, + dispatchers = dispatchers, + quotesRepository = quotesRepository, + ) + } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsPagingSource.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsPagingSource.kt new file mode 100644 index 0000000000..beb9f10b36 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsPagingSource.kt @@ -0,0 +1,77 @@ +package com.tangem.data.tokens.paging + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.CoinsResponse +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Quote +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider + +/** + * A PagingSource responsible for retrieving tokens from the Tangem Tech API and adding to them quotes information. + * + * @property api Tangem Tech API + * @property quotesRepository Repository providing quotes data. + * @property dispatchers Coroutine dispatchers provider. + * @property searchText The search text used to filter tokens. + */ +internal class CoinsPagingSource( + private val api: TangemTechApi, + private val quotesRepository: QuotesRepository, + private val dispatchers: CoroutineDispatcherProvider, + private val searchText: String?, +) : PagingSource() { + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(other = 1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(other = 1) + } + } + + override suspend fun load(params: LoadParams): LoadResult { + val page = params.key ?: 0 + + return com.tangem.utils.coroutines.runCatching(dispatchers.io) { + api.getCoins( + active = true, // TODO change when voting functionality is implemented + searchText = searchText, + offset = page * params.loadSize, + limit = params.loadSize, + ) + }.fold( + onSuccess = { response -> + val coinsIds = response.coins.map { coin -> + CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId(coin.id), + suffix = CryptoCurrency.ID.Suffix.RawID(coin.id), + ) + } + val quotes = quotesRepository.getQuotesSync(currenciesIds = coinsIds.toSet(), refresh = false) + + LoadResult.Page( + data = CoinsResponseConverter.convert( + CoinsData( + response.coins, + response.imageHost, + quotes, + ), + ), + prevKey = if (page == 0) null else page.minus(other = 1), + nextKey = if (response.coins.isEmpty()) null else page.plus(other = 1), + ) + }, + onFailure = { LoadResult.Error(it) }, + ) + } +} + +data class CoinsData( + val coins: List, + val imageHost: String?, + val quotes: Set, +) \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsResponseConverter.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsResponseConverter.kt new file mode 100644 index 0000000000..7735b24528 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsResponseConverter.kt @@ -0,0 +1,51 @@ +package com.tangem.data.tokens.paging + +import com.tangem.blockchain.common.Blockchain +import com.tangem.data.tokens.utils.getNetworkStandardType +import com.tangem.datasource.api.tangemTech.models.CoinsResponse +import com.tangem.domain.common.extensions.fromNetworkId +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Token +import com.tangem.utils.converter.Converter + +/** + * Converter from data model [CoinsResponse] to list of domain models [Token] + */ +internal object CoinsResponseConverter : Converter> { + + override fun convert(value: CoinsData): List { + return value.coins.map { coin -> + val id = CryptoCurrency.ID( + CryptoCurrency.ID.Prefix.TOKEN_PREFIX, + CryptoCurrency.ID.Body.NetworkId(coin.id), + CryptoCurrency.ID.Suffix.RawID(coin.id), + ) + val quote = value.quotes.find { it.rawCurrencyId == id.rawCurrencyId } + Token( + id = id.rawCurrencyId ?: coin.name, + name = coin.name, + symbol = coin.symbol, + iconUrl = getIconUrl(coin.id, value.imageHost), + isAvailable = coin.active, + networks = coin.networks.mapNotNull { network -> + val blockchain = Blockchain.fromNetworkId(network.networkId) ?: return@mapNotNull null + Token.Network( + networkId = network.networkId, + standardType = getNetworkStandardType(blockchain).name, + address = network.contractAddress, + iconUrl = getIconUrl(network.networkId, value.imageHost), + decimalCount = network.decimalCount?.toInt(), + ) + }, + quote = quote, + ) + } + } +} + +internal fun getIconUrl(id: String, imageHost: String? = null): String { + return "${imageHost ?: DEFAULT_IMAGE_HOST}large/$id.png" +} + +private const val DEFAULT_IMAGE_HOST = + "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/" \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensListRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensListRepository.kt new file mode 100644 index 0000000000..d4f2d9a9a2 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensListRepository.kt @@ -0,0 +1,45 @@ +package com.tangem.data.tokens.repository + +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.tangem.data.tokens.paging.CoinsPagingSource +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.tokens.repository.TokensListRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow + +/** + * Default repository implementation for managing operations related to a complete set of tokens + * + * @property tangemTechApi Tangem Tech API + * @property dispatchers coroutine dispatchers provider + * @property quotesRepository responsible for providing cryptocurrency quotes data + * + */ +internal class DefaultTokensListRepository( + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, + private val quotesRepository: QuotesRepository, +) : TokensListRepository { + + override fun getTokens(searchText: String?): Flow> { + return Pager( + config = PagingConfig( + pageSize = 100, + prefetchDistance = 70, + enablePlaceholders = false, + ), + pagingSourceFactory = { + CoinsPagingSource( + api = tangemTechApi, + dispatchers = dispatchers, + searchText = searchText, + quotesRepository = quotesRepository, + ) + }, + ).flow + } +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt index 5e87555133..21ef628101 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt @@ -50,7 +50,7 @@ private fun getNetworkDerivationPath( } } -private fun getNetworkStandardType(blockchain: Blockchain): Network.StandardType { +internal fun getNetworkStandardType(blockchain: Blockchain): Network.StandardType { return when (blockchain) { Blockchain.Ethereum, Blockchain.EthereumTestnet -> Network.StandardType.ERC20 Blockchain.BSC, Blockchain.BSCTestnet -> Network.StandardType.BEP20 diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index c146658684..8fabb669f0 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -22,6 +22,9 @@ dependencies { /** Project - Other */ implementation(projects.core.utils) + /** Android - Other */ + implementation(deps.androidx.paging.runtime) + /** Utils */ implementation(deps.jodatime) implementation(deps.reKotlin) diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt index 899574d5e4..9f5dfbe387 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt @@ -3,14 +3,17 @@ package com.tangem.domain.tokens.model import java.math.BigDecimal /** - * Represents a financial quote for a specific cryptocurrency, including its fiat exchange rate and price change. + * Represents financial information for a specific cryptocurrency, including its fiat exchange rate and price change. * - * @property rawCurrencyId The unique identifier of the token for which the quote is provided. + * @property rawCurrencyId The unique identifier of the cryptocurrency for which the financial information is provided. * @property fiatRate The current fiat exchange rate for the cryptocurrency. * @property priceChange The price change for the cryptocurrency. + * @property values The values representing the cryptocurrency's price changes over a 24-hour period, + * suitable for chart plotting. */ data class Quote( val rawCurrencyId: String, val fiatRate: BigDecimal, val priceChange: BigDecimal, + val values: List? = null, ) \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Token.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Token.kt new file mode 100644 index 0000000000..ed26e356b8 --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Token.kt @@ -0,0 +1,41 @@ +package com.tangem.domain.tokens.model + +/** + * Represents a domain model for a token (as part of a general list of tokens). + * + * @property id The unique identifier of the token. + * @property networks List of networks associated with the token. + * @property isAvailable Indicates whether this token is supported in Tangem app. + * @property quote Equivalent prices for the token. + * @property name The name of the token. + * @property symbol The brief name of the token, e.g., "BTC". + * @property iconUrl URL of the token's icon. + */ +data class Token( + val id: String, + val networks: List, + val isAvailable: Boolean, + val quote: Quote?, + val name: String, + val symbol: String, + val iconUrl: String, +) { + + /** + * Represents a domain model for a network associated with a token. + * + * @property networkId The unique identifier of the network. + * @property standardType The type of blockchain associated with the network. + * @property address The contract address of the token on the current network. + * It is null for the main currencies of the network. + * @property iconUrl URL of the network's icon. + * @property decimalCount The decimal count associated with the token on the network. + */ + data class Network( + val networkId: String, + val standardType: String, + val address: String?, + val iconUrl: String, + val decimalCount: Int?, + ) +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetGlobalTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetGlobalTokenListUseCase.kt new file mode 100644 index 0000000000..74ddd5b4e4 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetGlobalTokenListUseCase.kt @@ -0,0 +1,22 @@ +package com.tangem.domain.tokens + +import androidx.paging.PagingData +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.tokens.repository.TokensListRepository +import kotlinx.coroutines.flow.Flow + +/** + * Use case for retrieving a complete set of tokens with their quotes (filtered by a search text if provided). + * + * @property repository The repository responsible for fetching and preparing the list of tokens with their quotes. + */ +class GetGlobalTokenListUseCase(private val repository: TokensListRepository) { + + /** + * @param searchText The text used for filtering the list of tokens (not required). + * @return A [Flow] emitting [PagingData] containing the tokens with their quotes. + */ + operator fun invoke(searchText: String?): Flow> { + return repository.getTokens(searchText = searchText?.ifBlank(defaultValue = { null })) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensListRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensListRepository.kt new file mode 100644 index 0000000000..96db81b6af --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensListRepository.kt @@ -0,0 +1,20 @@ +package com.tangem.domain.tokens.repository + +import androidx.paging.PagingData +import com.tangem.domain.tokens.model.Token +import kotlinx.coroutines.flow.Flow + +/** + * Repository interface for managing operations related to a complete set of tokens + * (not associated with any specific card). + * */ +interface TokensListRepository { + + /** + * Retrieves a list of available tokens with quotes, can be filtered based on the provided search text. + * + * @param searchText The search text used to filter tokens. + * @return A [Flow] emitting [PagingData] containing the tokens with quotes matching the search criteria. + */ + fun getTokens(searchText: String?): Flow> +} \ No newline at end of file diff --git a/features/manage-tokens/impl/build.gradle.kts b/features/manage-tokens/impl/build.gradle.kts index 76bd921f71..fab77971a3 100644 --- a/features/manage-tokens/impl/build.gradle.kts +++ b/features/manage-tokens/impl/build.gradle.kts @@ -47,6 +47,9 @@ dependencies { implementation(projects.core.ui) implementation(projects.core.utils) + /** Project - Data */ + implementation(projects.data.tokens) + /** Domain modules */ implementation(projects.common) implementation(projects.domain.card) From 95510a665c03ecc484d511d0bf27340af5fc853b Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 30 Nov 2023 19:09:13 +0300 Subject: [PATCH 6/8] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../feature/wallet/presentation/router/DefaultWalletRouter.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 2a612cd92b..6ef8ce45d1 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 2a612cd92b1c78b99c917d0fe66342831f801e5e +Subproject commit 6ef8ce45d183905b5752e2d33c1d8bf2f4bcace6 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index e5ad37394c..8807297f9f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -63,6 +63,7 @@ internal class DefaultWalletRouter( val viewModel = hiltViewModel().apply { router = this@DefaultWalletRouter } + LocalLifecycleOwner.current.lifecycle.addObserver(viewModel) WalletScreen(state = viewModel.uiState) } From 34ffa9a1e719612fe0e7fac58bea264d316abde1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Dec 2023 19:40:54 +0400 Subject: [PATCH 7/8] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../java/com/tangem/tap/TapApplication.kt | 6 ++++ .../amplitude/AmplitudeAnalyticsHandler.kt | 4 +-- .../appsFlyer/AppsFlyerAnalyticsHandler.kt | 4 +-- .../firebase/FirebaseAnalyticsHandler.kt | 4 +-- core/analytics/build.gradle.kts | 9 +++--- .../core/analytics/models/AnalyticsEvent.kt | 5 ++- .../analytics/models/OneTimeAnalyticsEvent.kt | 6 ++++ core/analytics/src/main/AndroidManifest.xml | 2 -- .../core/analytics/api/EventFilterApi.kt | 2 +- .../core/analytics/api/EventHandlerApi.kt | 6 ++-- .../core/analytics/di/AnalyticsModule.kt | 9 +++++- .../analytics/filter/OneTimeEventFilter.kt | 30 +++++++++++++++++ .../repository/AnalyticsRepository.kt | 8 +++++ .../local/preferences/PreferencesKeys.kt | 2 ++ data/analytics/.gitignore | 1 + data/analytics/build.gradle.kts | 32 +++++++++++++++++++ .../analytics/DefaultAnalyticsRepository.kt | 27 ++++++++++++++++ .../data/analytics/di/AnalyticsDataModule.kt | 19 +++++++++++ settings.gradle.kts | 2 +- 20 files changed, 158 insertions(+), 21 deletions(-) create mode 100644 core/analytics/models/src/main/java/com/tangem/core/analytics/models/OneTimeAnalyticsEvent.kt delete mode 100644 core/analytics/src/main/AndroidManifest.xml create mode 100644 core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt create mode 100644 core/analytics/src/main/java/com/tangem/core/analytics/repository/AnalyticsRepository.kt create mode 100644 data/analytics/.gitignore create mode 100644 data/analytics/build.gradle.kts create mode 100644 data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt create mode 100644 data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e3f7a4fcde..a3a05911c7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -74,6 +74,7 @@ dependencies { implementation(projects.data.tokens) implementation(projects.data.txhistory) implementation(projects.data.wallets) + implementation(projects.data.analytics) /** Features */ implementation(projects.features.onboarding) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 420a6ff091..0aecd2719a 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -14,6 +14,7 @@ import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.datasource.api.common.MoshiConverter @@ -216,6 +217,9 @@ internal class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var sendFeatureToggles: SendFeatureToggles + + @Inject + lateinit var oneTimeEventFilter: OneTimeEventFilter // endregion Injected override fun onCreate() { @@ -353,6 +357,8 @@ internal class TapApplication : Application(), ImageLoaderFactory { factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder()) factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder()) + factory.addFilter(oneTimeEventFilter) + val buildData = AnalyticsHandlerBuilder.Data( application = application, config = config, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt index a1daeb7ccf..5eed564c97 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt @@ -9,8 +9,8 @@ class AmplitudeAnalyticsHandler( override fun id(): String = ID - override fun send(event: String, params: Map) { - client.logEvent(event, params) + override fun send(eventId: String, params: Map) { + client.logEvent(eventId, params) } companion object { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt index c4b12fc278..710c7308e6 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt @@ -12,8 +12,8 @@ class AppsFlyerAnalyticsHandler( override fun id(): String = ID - override fun send(event: String, params: Map) { - client.logEvent(event, params) + override fun send(eventId: String, params: Map) { + client.logEvent(eventId, params) } override fun send(event: AnalyticsEvent) { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt index e445597e8f..10b399382c 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt @@ -14,8 +14,8 @@ class FirebaseAnalyticsHandler( override fun id(): String = ID - override fun send(event: String, params: Map) { - client.logEvent(event, params) + override fun send(eventId: String, params: Map) { + client.logEvent(eventId, params) } override fun send(event: AnalyticsEvent) { diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts index 29b4cfb591..663fb0f33e 100644 --- a/core/analytics/build.gradle.kts +++ b/core/analytics/build.gradle.kts @@ -1,19 +1,18 @@ plugins { - alias(deps.plugins.android.library) - alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.jvm) alias(deps.plugins.kotlin.kapt) - alias(deps.plugins.hilt.android) id("configuration") } dependencies { /** DI */ - implementation(deps.hilt.android) + implementation(deps.hilt.core) kapt(deps.hilt.kapt) /** Core shouldn't depends on core, but in case with utils and logging its necessary */ implementation(projects.core.utils) - implementation(projects.core.analytics.models) + + implementation(deps.kotlin.coroutines) } \ No newline at end of file diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt index e60b14d1ee..025a6c1038 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt @@ -8,4 +8,7 @@ open class AnalyticsEvent( val event: String, var params: Map = mapOf(), val error: Throwable? = null, -) \ No newline at end of file +) { + + val id: String = "[$category] $event" +} \ No newline at end of file diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/OneTimeAnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/OneTimeAnalyticsEvent.kt new file mode 100644 index 0000000000..82d9991c71 --- /dev/null +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/OneTimeAnalyticsEvent.kt @@ -0,0 +1,6 @@ +package com.tangem.core.analytics.models + +interface OneTimeAnalyticsEvent { + + val oneTimeEventId: String +} \ No newline at end of file diff --git a/core/analytics/src/main/AndroidManifest.xml b/core/analytics/src/main/AndroidManifest.xml deleted file mode 100644 index 9270c6ee97..0000000000 --- a/core/analytics/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt index 4de7e4aee8..dc94652208 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt @@ -16,7 +16,7 @@ interface AnalyticsEventFilter { * An internal filter check that, on external or internal conditions, recognizes the possibility of * sending an event. */ - fun canBeSent(event: AnalyticsEvent): Boolean + suspend fun canBeSent(event: AnalyticsEvent): Boolean /** * Performs a check to see if the event can be dispatched by a specific handler diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt index 0a5f3179de..7bedfebff3 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt @@ -12,13 +12,11 @@ interface AnalyticsEventHandler { interface AnalyticsHandler : AnalyticsEventHandler { fun id(): String - fun send(event: String, params: Map = emptyMap()) + fun send(eventId: String, params: Map = emptyMap()) override fun send(event: AnalyticsEvent) { - send(prepareEventString(event), event.params) + send(event.id, event.params) } - - fun prepareEventString(event: AnalyticsEvent): String = "[${event.category}] ${event.event}" } interface ErrorEventHandler { diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt b/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt index 3b12478c96..b4bb704732 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/di/AnalyticsModule.kt @@ -2,6 +2,8 @@ package com.tangem.core.analytics.di import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.filter.OneTimeEventFilter +import com.tangem.core.analytics.repository.AnalyticsRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -10,11 +12,16 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -class AnalyticsModule { +internal object AnalyticsModule { @Singleton @Provides fun provideAnalyticsHandler(): AnalyticsEventHandler { return Analytics // todo replace after refactoring calling Analytics in whole project } + + @Provides + fun provideOneTimeEventFilter(analyticsRepository: AnalyticsRepository): OneTimeEventFilter { + return OneTimeEventFilter(analyticsRepository) + } } \ No newline at end of file diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt b/core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt new file mode 100644 index 0000000000..c224eff75b --- /dev/null +++ b/core/analytics/src/main/java/com/tangem/core/analytics/filter/OneTimeEventFilter.kt @@ -0,0 +1,30 @@ +package com.tangem.core.analytics.filter + +import com.tangem.core.analytics.api.AnalyticsEventFilter +import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.OneTimeAnalyticsEvent +import com.tangem.core.analytics.repository.AnalyticsRepository + +class OneTimeEventFilter( + private val analyticsRepository: AnalyticsRepository, +) : AnalyticsEventFilter { + + override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is OneTimeAnalyticsEvent + + override suspend fun canBeSent(event: AnalyticsEvent): Boolean { + if (event !is OneTimeAnalyticsEvent) return true + + val isSent = analyticsRepository.checkIsEventSent(event.oneTimeEventId) + + if (!isSent) { + analyticsRepository.setIsEventSent(event.oneTimeEventId) + } + + return !isSent + } + + override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean { + return canBeAppliedTo(event) + } +} \ No newline at end of file diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/repository/AnalyticsRepository.kt b/core/analytics/src/main/java/com/tangem/core/analytics/repository/AnalyticsRepository.kt new file mode 100644 index 0000000000..859220527a --- /dev/null +++ b/core/analytics/src/main/java/com/tangem/core/analytics/repository/AnalyticsRepository.kt @@ -0,0 +1,8 @@ +package com.tangem.core.analytics.repository + +interface AnalyticsRepository { + + suspend fun checkIsEventSent(eventId: String): Boolean + + suspend fun setIsEventSent(eventId: String) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 6eda0886c9..0ad9c10237 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -34,6 +34,8 @@ object PreferencesKeys { val BALANCE_HIDING_SETTINGS_KEY by lazy { stringPreferencesKey(name = "balanceHidingSettings") } val WALLETS_SCROLL_PREVIEW_KEY by lazy { booleanPreferencesKey(name = "walletsScrollPreview") } + + val SENT_ONE_TIME_EVENTS_KEY by lazy { stringPreferencesKey(name = "sentOneTimeEvents") } } /** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore */ diff --git a/data/analytics/.gitignore b/data/analytics/.gitignore new file mode 100644 index 0000000000..3a48006eea --- /dev/null +++ b/data/analytics/.gitignore @@ -0,0 +1 @@ +/buld diff --git a/data/analytics/build.gradle.kts b/data/analytics/build.gradle.kts new file mode 100644 index 0000000000..258c49ea37 --- /dev/null +++ b/data/analytics/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + id("configuration") +} + +android { + namespace = "com.tangem.data.analytics" +} + +dependencies { + + /** Project - Analytics */ + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) + + /** Project - Data */ + implementation(projects.core.datasource) + implementation(projects.data.common) + + /** AndroidX */ + implementation(deps.androidx.datastore) + + /** DI */ + implementation(deps.hilt.core) + kapt(deps.hilt.kapt) + + /** Other */ + implementation(deps.kotlin.coroutines) + implementation(deps.timber) +} \ No newline at end of file diff --git a/data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt b/data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt new file mode 100644 index 0000000000..9235045bae --- /dev/null +++ b/data/analytics/src/main/kotlin/com/tangem/data/analytics/DefaultAnalyticsRepository.kt @@ -0,0 +1,27 @@ +package com.tangem.data.analytics + +import com.tangem.core.analytics.repository.AnalyticsRepository +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectListSync + +internal class DefaultAnalyticsRepository( + private val appPreferencesStore: AppPreferencesStore, +) : AnalyticsRepository { + + override suspend fun checkIsEventSent(eventId: String): Boolean { + val sentEvents = appPreferencesStore + .getObjectListSync(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY) + + return eventId in sentEvents + } + + override suspend fun setIsEventSent(eventId: String) { + appPreferencesStore.editData { mutablePreferences -> + val sentEvents = mutablePreferences.getObject>(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY) + val updatedSentEvents = sentEvents.orEmpty() + eventId + + mutablePreferences.setObject(PreferencesKeys.SENT_ONE_TIME_EVENTS_KEY, updatedSentEvents) + } + } +} \ No newline at end of file diff --git a/data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt b/data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt new file mode 100644 index 0000000000..ed3e2b04ac --- /dev/null +++ b/data/analytics/src/main/kotlin/com/tangem/data/analytics/di/AnalyticsDataModule.kt @@ -0,0 +1,19 @@ +package com.tangem.data.analytics.di + +import com.tangem.core.analytics.repository.AnalyticsRepository +import com.tangem.data.analytics.DefaultAnalyticsRepository +import com.tangem.datasource.local.preferences.AppPreferencesStore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +internal object AnalyticsDataModule { + + @Provides + fun provideAnalyticsRepository(appPreferencesStore: AppPreferencesStore): AnalyticsRepository { + return DefaultAnalyticsRepository(appPreferencesStore) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 59ac1ba906..1abc2626ae 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -128,7 +128,6 @@ include(":domain:app-theme:models") include(":domain:balance-hiding") include(":domain:balance-hiding:models") include(":domain:transaction") - // endregion Domain modules // region Data modules @@ -142,4 +141,5 @@ include(":data:source:preferences") include(":data:settings") include(":data:txhistory") include(":data:wallets") +include(":data:analytics") // endregion Data modules \ No newline at end of file From cad55c940886b7502f5f1f93e57758612959e5db Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Dec 2023 13:12:53 +0400 Subject: [PATCH 8/8] Updated on 2026-08-14 --- .../BiometricUserWalletsListManager.kt | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index af472b3608..c285a15c80 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -67,15 +67,17 @@ internal class BiometricUserWalletsListManager( } } .map { - if (throwIfNotAllWalletsUnlocked && state.value.userWallets.any(UserWallet::isLocked)) { - Timber.e("Not all user wallets have been unlocked") + val userWallets = state.value.userWallets + + if (throwIfNotAllWalletsUnlocked && userWallets.any(UserWallet::isLocked)) { + Timber.e("Some user wallets remain locked") throw UserWalletsListError.NotAllUserWalletsUnlocked } val selectedUserWallet = selectedUserWalletSync if (selectedUserWallet == null || selectedUserWallet.isLocked) { - Timber.e("Unable to find selected user wallet") - throw UserWalletsListError.NoUserWalletSelected + findAndSetUnlockedUserWallet(userWallets) + ?: throw UserWalletsListError.NoUserWalletSelected } else { selectedUserWallet } @@ -280,13 +282,14 @@ internal class BiometricUserWalletsListManager( prevSelectedWalletId: UserWalletId?, userWallets: List, ): UserWalletId? { - val findUnlockedAndSet = { - userWallets.firstOrNull { !it.isLocked } - ?.walletId - ?.also { selectedUserWalletRepository.set(it) } - } + return prevSelectedWalletId + ?: (selectedUserWalletRepository.get() ?: findAndSetUnlockedUserWallet(userWallets)?.walletId) + } - return prevSelectedWalletId ?: (selectedUserWalletRepository.get() ?: findUnlockedAndSet()) + private fun findAndSetUnlockedUserWallet(userWallets: List): UserWallet? { + return userWallets + .firstOrNull { !it.isLocked } + ?.also { selectedUserWalletRepository.set(it.walletId) } } private fun changeSelectedUserWalletIdIfNeeded(walletsIdsToRemove: List) {