Updated on 2026-08-14
This commit is contained in:
commit
8eb1a28f29
236 changed files with 3852 additions and 2068 deletions
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.common.utils
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
|
||||
fun getClipboardText(context: Context): String? {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
return if (clipboard.hasPrimaryClip()) {
|
||||
clipboard.primaryClip?.getItemAt(0)?.text?.toString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun setClipboardText(context: Context, text: String?) {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("label", text)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
}
|
||||
|
|
@ -85,4 +85,13 @@ fun BaseTestCase.openDeviceSettingsScreen() {
|
|||
step("Click on 'Device settings' button") {
|
||||
onWalletSettingsScreen { deviceSettingsButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.openWalletConnectScreen() {
|
||||
step("Click 'More' button on TopBar") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Wallet Connect' button") {
|
||||
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.screens.onWalletConnectScreen
|
|||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.checkWalletConnectBottomSheet() {
|
||||
waitForIdle()
|
||||
step("Assert 'Wallet Connect' bottom sheet title is displayed") {
|
||||
onWalletConnectBottomSheet { title.assertIsDisplayed() }
|
||||
}
|
||||
|
|
@ -60,34 +61,64 @@ fun BaseTestCase.checkWalletConnectBottomSheet() {
|
|||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.checkWalletConnectScreen() {
|
||||
fun BaseTestCase.checkWalletConnectScreen(withConnections: Boolean) {
|
||||
waitForIdle()
|
||||
step("Assert 'Wallet Connect' title is displayed") {
|
||||
onWalletConnectScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'More' button is displayed") {
|
||||
onWalletConnectScreen { moreButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert wallet name is displayed") {
|
||||
onWalletConnectScreen { walletName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app icon is displayed") {
|
||||
onWalletConnectScreen { appIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app name is displayed") {
|
||||
onWalletConnectScreen { appName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert approve icon is displayed") {
|
||||
onWalletConnectScreen { approveIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app URL is displayed") {
|
||||
onWalletConnectScreen { appUrl.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'New Connection' button is displayed") {
|
||||
onWalletConnectScreen { newConnectionButton.assertIsDisplayed() }
|
||||
}
|
||||
if (withConnections) {
|
||||
step("Assert 'More' button is displayed") {
|
||||
onWalletConnectScreen { moreButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert wallet name is displayed") {
|
||||
onWalletConnectScreen { walletName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app icon is displayed") {
|
||||
onWalletConnectScreen { appIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app name is displayed") {
|
||||
onWalletConnectScreen { appName.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert approve icon is displayed") {
|
||||
onWalletConnectScreen { approveIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert app URL is displayed") {
|
||||
onWalletConnectScreen { appUrl.assertIsDisplayed() }
|
||||
}
|
||||
} else {
|
||||
step("Assert wallet name is not displayed") {
|
||||
onWalletConnectScreen { walletName.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert app icon is not displayed") {
|
||||
onWalletConnectScreen { appIcon.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert app name is not displayed") {
|
||||
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert approve icon is not displayed") {
|
||||
onWalletConnectScreen { approveIcon.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert app URL is not displayed") {
|
||||
onWalletConnectScreen { appUrl.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' image is displayed") {
|
||||
onWalletConnectScreen { walletConnectImage.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'No session' title is displayed") {
|
||||
onWalletConnectScreen { noSessionTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'No session' text is displayed") {
|
||||
onWalletConnectScreen { noSessionText.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
|
||||
waitForIdle()
|
||||
step("Assert connection details title is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { title.assertIsDisplayed() }
|
||||
}
|
||||
|
|
@ -128,7 +159,7 @@ fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
|
|||
onWalletConnectDetailsBottomSheet { connectedNetworkIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert connected dApp name: '$dAppName'") {
|
||||
onWalletConnectDetailsBottomSheet { connectedNetworkName.assertTextContains(dAppName) }
|
||||
onWalletConnectDetailsBottomSheet { appName.assertTextContains(dAppName) }
|
||||
}
|
||||
step("Assert connected network symbol is displayed") {
|
||||
onWalletConnectDetailsBottomSheet { connectedNetworkSymbol.assertIsDisplayed() }
|
||||
|
|
|
|||
|
|
@ -9,13 +9,11 @@ import io.github.kakaocup.compose.node.element.ComposeScreen
|
|||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.features.walletconnect.impl.R as WalletConnectImplR
|
||||
|
||||
class WalletConnectBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<WalletConnectBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasText(getResourceString(WalletConnectImplR.string.wc_wallet_connect))
|
||||
hasTestTag(WalletConnectBottomSheetTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,21 @@ class WalletConnectPageObject(semanticsProvider: SemanticsNodeInteractionsProvid
|
|||
hasText(getResourceString(R.string.wc_new_connection))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val walletConnectImage: KNode = child {
|
||||
hasTestTag(WalletConnectScreenTestTags.WALLET_CONNECT_IMAGE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val noSessionTitle: KNode = child {
|
||||
hasText(getResourceString(R.string.wc_no_sessions_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val noSessionText: KNode = child {
|
||||
hasText(getResourceString(R.string.wc_no_sessions_desc))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onWalletConnectScreen(function: WalletConnectPageObject.() -> Unit) =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class WalletConnectScanQrPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<WalletConnectScanQrPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val pasteFromClipboardButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.wallet_connect_paste_from_clipboard))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onWalletConnectScanQrScreen(function: WalletConnectScanQrPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -2,12 +2,13 @@ package com.tangem.tests
|
|||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||
import com.tangem.common.extensions.SwipeDirection
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.swipeVertical
|
||||
import com.tangem.common.utils.getWcUri
|
||||
import com.tangem.common.utils.setClipboardText
|
||||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
|
|
@ -21,7 +22,7 @@ class WalletConnectTest : BaseTestCase() {
|
|||
@DisplayName("WC (React App): open session from deeplink on main screen")
|
||||
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||
@Test
|
||||
fun openWalletConnectSessionOnMainScreen() {
|
||||
fun openWalletConnectSessionOnMainScreenTest() {
|
||||
val balance = TOTAL_BALANCE
|
||||
val dAppName = "React App"
|
||||
val deepLinkUri = getWcUri()
|
||||
|
|
@ -37,19 +38,28 @@ class WalletConnectTest : BaseTestCase() {
|
|||
openAppByDeepLink(deepLinkUri)
|
||||
}
|
||||
step("Check 'Wallet Connect' bottom sheet") {
|
||||
checkWalletConnectBottomSheet()
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
checkWalletConnectBottomSheet()
|
||||
}
|
||||
}
|
||||
step("Assert 'Connect' button is enabled") {
|
||||
onWalletConnectBottomSheet { connectButton.assertIsEnabled() }
|
||||
}
|
||||
step("Click on 'Connect' button") {
|
||||
waitForIdle()
|
||||
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||
}
|
||||
step("Click 'More' button on TopBar") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
step("Assert 'Connect' button is not displayed") {
|
||||
waitForIdle()
|
||||
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Click on 'Wallet Connect' button") {
|
||||
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
||||
step("Open 'Wallet Connect' screen") {
|
||||
openWalletConnectScreen()
|
||||
}
|
||||
step("Check 'Wallet Connect' screen") {
|
||||
checkWalletConnectScreen()
|
||||
step("Check 'Wallet Connect' screen with connections") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
checkWalletConnectScreen(withConnections = true)
|
||||
}
|
||||
}
|
||||
step("Click on app icon") {
|
||||
onWalletConnectScreen { appIcon.performClick() }
|
||||
|
|
@ -57,11 +67,11 @@ class WalletConnectTest : BaseTestCase() {
|
|||
step("Check 'Wallet Connect' details bottom sheet") {
|
||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||
}
|
||||
step("Click on 'Disconnect button' is displayed") {
|
||||
step("Click on 'Disconnect' button") {
|
||||
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||
}
|
||||
step("Assert connection is not displayed") {
|
||||
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
||||
step("Check 'Wallet Connect' screen without connections") {
|
||||
checkWalletConnectScreen(withConnections = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +80,7 @@ class WalletConnectTest : BaseTestCase() {
|
|||
@DisplayName("WC (React App): open session from deeplink not on main screen")
|
||||
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||
@Test
|
||||
fun openWalletConnectSessionNotOnMainScreen() {
|
||||
fun openWalletConnectSessionNotOnMainScreenTest() {
|
||||
val balance = TOTAL_BALANCE
|
||||
val dAppName = "React App"
|
||||
val deepLinkUri = getWcUri()
|
||||
|
|
@ -82,41 +92,44 @@ class WalletConnectTest : BaseTestCase() {
|
|||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.clickWithAssertion() }
|
||||
step("Open 'Wallet Connect' screen") {
|
||||
openWalletConnectScreen()
|
||||
checkWalletConnectScreen(false)
|
||||
}
|
||||
step("Create WC session buy deeplink") {
|
||||
openAppByDeepLink(deepLinkUri)
|
||||
}
|
||||
step("Check 'Wallet Connect' bottom sheet") {
|
||||
checkWalletConnectBottomSheet()
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
checkWalletConnectBottomSheet()
|
||||
}
|
||||
}
|
||||
step("Click on 'Connect' button") {
|
||||
waitForIdle()
|
||||
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||
}
|
||||
step("Click 'More' button on TopBar") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
step("Assert 'Connect' button is not displayed") {
|
||||
waitForIdle()
|
||||
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Click on 'Wallet Connect' button") {
|
||||
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Wallet Connect' bottom sheet is displayed") {
|
||||
onWalletConnectBottomSheet { connectButton.clickWithAssertion() }
|
||||
}
|
||||
step("Check 'Wallet Connect' screen") {
|
||||
checkWalletConnectScreen()
|
||||
step("Check 'Wallet Connect' screen with connections") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
checkWalletConnectScreen(withConnections = true)
|
||||
}
|
||||
}
|
||||
step("Click on app icon") {
|
||||
onWalletConnectScreen { appIcon.performClick() }
|
||||
}
|
||||
step("Check 'Wallet Connect' details bottom sheet") {
|
||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||
}
|
||||
}
|
||||
step("Click on 'Disconnect button' is displayed") {
|
||||
step("Click on 'Disconnect' button") {
|
||||
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||
}
|
||||
step("Assert connection is not displayed") {
|
||||
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
||||
step("Check 'Wallet Connect' screen without connections") {
|
||||
checkWalletConnectScreen(withConnections = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -125,9 +138,10 @@ class WalletConnectTest : BaseTestCase() {
|
|||
@DisplayName("WC (React App): open session from deeplink ")
|
||||
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||
@Test
|
||||
fun openWalletConnectSession() {
|
||||
fun openWalletConnectSessionTest() {
|
||||
val balance = TOTAL_BALANCE
|
||||
val dAppName = "React App"
|
||||
val packageName = BuildConfig.APPLICATION_ID
|
||||
val deepLinkUri = getWcUri()
|
||||
|
||||
setupHooks().run {
|
||||
|
|
@ -137,32 +151,28 @@ class WalletConnectTest : BaseTestCase() {
|
|||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Open recent apps") {
|
||||
device.uiDevice.pressRecentApps()
|
||||
}
|
||||
step("Stop app by swipe") {
|
||||
swipeVertical(SwipeDirection.UP, startHeightRatio = 0.8f)
|
||||
step("Kill app") {
|
||||
device.apps.kill(packageName)
|
||||
}
|
||||
step("Create WC session buy deeplink") {
|
||||
openAppByDeepLink(deepLinkUri)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Check 'Wallet Connect' bottom sheet") {
|
||||
checkWalletConnectBottomSheet()
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
checkWalletConnectBottomSheet()
|
||||
}
|
||||
}
|
||||
step("Click on 'Connect' button") {
|
||||
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||
}
|
||||
step("Click 'More' button on TopBar") {
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
step("Assert 'Connect' button is not displayed") {
|
||||
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Click on 'Wallet Connect' button") {
|
||||
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
||||
step("Open 'Wallet Connect' screen") {
|
||||
openWalletConnectScreen()
|
||||
}
|
||||
step("Check 'Wallet Connect' screen") {
|
||||
checkWalletConnectScreen()
|
||||
step("Check 'Wallet Connect' screen with connections") {
|
||||
checkWalletConnectScreen(withConnections = true)
|
||||
}
|
||||
step("Click on app icon") {
|
||||
onWalletConnectScreen { appIcon.performClick() }
|
||||
|
|
@ -170,11 +180,72 @@ class WalletConnectTest : BaseTestCase() {
|
|||
step("Check 'Wallet Connect' details bottom sheet") {
|
||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||
}
|
||||
step("Click on 'Disconnect button' is displayed") {
|
||||
step("Click on 'Disconnect' button") {
|
||||
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||
}
|
||||
step("Assert connection is not displayed") {
|
||||
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
||||
step("Check 'Wallet Connect' screen without connections") {
|
||||
checkWalletConnectScreen(withConnections = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("887")
|
||||
@DisplayName("WC: open session by 'Paste from clipboard' button")
|
||||
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||
@Test
|
||||
fun openWalletConnectSessionByClipboardLinkTest() {
|
||||
val balance = TOTAL_BALANCE
|
||||
val dAppName = "React App"
|
||||
val context = device.context
|
||||
val deepLinkUri = getWcUri()
|
||||
|
||||
setupHooks().run {
|
||||
step("Set URI to clipboard") {
|
||||
setClipboardText(context, deepLinkUri)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses(balance)
|
||||
}
|
||||
step("Open 'Wallet Connect' screen") {
|
||||
openWalletConnectScreen()
|
||||
}
|
||||
step("Click 'New connection' button") {
|
||||
onWalletConnectScreen { newConnectionButton.performClick() }
|
||||
}
|
||||
step("CLick 'Paste from clipboard' button") {
|
||||
onWalletConnectScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
|
||||
}
|
||||
step("Check 'Wallet Connect' bottom sheet") {
|
||||
waitForIdle()
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
checkWalletConnectBottomSheet()
|
||||
}
|
||||
}
|
||||
step("Click on 'Connect' button") {
|
||||
waitForIdle()
|
||||
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||
}
|
||||
step("Assert 'Connect' button is not displayed") {
|
||||
waitForIdle()
|
||||
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Check 'Wallet Connect' screen with connections") {
|
||||
checkWalletConnectScreen(withConnections = true)
|
||||
}
|
||||
step("Click on app icon") {
|
||||
onWalletConnectScreen { appIcon.performClick() }
|
||||
}
|
||||
step("Check 'Wallet Connect' details bottom sheet") {
|
||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||
}
|
||||
step("Click on 'Disconnect' button") {
|
||||
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||
}
|
||||
step("Check 'Wallet Connect' screen without connections") {
|
||||
checkWalletConnectScreen(withConnections = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,11 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
|||
secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true)
|
||||
}
|
||||
|
||||
override suspend fun clear(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
||||
override suspend fun clearOrderId(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||
}
|
||||
|
||||
override suspend fun clearAll(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
||||
secureStorage.delete(createKey(customerWalletAddress))
|
||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
|||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.domain.yield.supply.YieldSupplyMarketRepository
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
|
||||
|
|
@ -454,21 +454,17 @@ internal object WalletsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyApyFlowUseCase(
|
||||
yieldSupplyMarketRepository: YieldSupplyMarketRepository,
|
||||
): YieldSupplyApyFlowUseCase {
|
||||
fun provideYieldSupplyApyFlowUseCase(yieldSupplyRepository: YieldSupplyRepository): YieldSupplyApyFlowUseCase {
|
||||
return YieldSupplyApyFlowUseCase(
|
||||
yieldSupplyMarketRepository = yieldSupplyMarketRepository,
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyApyUpdateUseCase(
|
||||
yieldSupplyMarketRepository: YieldSupplyMarketRepository,
|
||||
): YieldSupplyApyUpdateUseCase {
|
||||
fun provideYieldSupplyApyUpdateUseCase(yieldSupplyRepository: YieldSupplyRepository): YieldSupplyApyUpdateUseCase {
|
||||
return YieldSupplyApyUpdateUseCase(
|
||||
yieldSupplyMarketRepository = yieldSupplyMarketRepository,
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.tangem.domain.blockaid.BlockAidGasEstimate
|
|||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.transaction.error.FeeErrorResolver
|
||||
import com.tangem.domain.yield.supply.YieldSupplyErrorResolver
|
||||
import com.tangem.domain.yield.supply.YieldSupplyMarketRepository
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository
|
||||
import com.tangem.domain.yield.supply.usecase.*
|
||||
import dagger.Module
|
||||
|
|
@ -82,30 +82,54 @@ internal object YieldSupplyDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyGetTokenStatusUseCase(
|
||||
yieldSupplyMarketRepository: YieldSupplyMarketRepository,
|
||||
yieldSupplyRepository: YieldSupplyRepository,
|
||||
): YieldSupplyGetTokenStatusUseCase {
|
||||
return YieldSupplyGetTokenStatusUseCase(
|
||||
yieldSupplyMarketRepository = yieldSupplyMarketRepository,
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyGetApyUseCase(
|
||||
yieldSupplyMarketRepository: YieldSupplyMarketRepository,
|
||||
): YieldSupplyGetApyUseCase {
|
||||
fun provideYieldSupplyGetApyUseCase(yieldSupplyRepository: YieldSupplyRepository): YieldSupplyGetApyUseCase {
|
||||
return YieldSupplyGetApyUseCase(
|
||||
yieldSupplyMarketRepository = yieldSupplyMarketRepository,
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyGetChartUseCase(
|
||||
yieldSupplyMarketRepository: YieldSupplyMarketRepository,
|
||||
): YieldSupplyGetChartUseCase {
|
||||
fun provideYieldSupplyGetChartUseCase(yieldSupplyRepository: YieldSupplyRepository): YieldSupplyGetChartUseCase {
|
||||
return YieldSupplyGetChartUseCase(
|
||||
yieldSupplyMarketRepository = yieldSupplyMarketRepository,
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyIsAvailableUseCase(
|
||||
yieldSupplyRepository: YieldSupplyRepository,
|
||||
): YieldSupplyIsAvailableUseCase {
|
||||
return YieldSupplyIsAvailableUseCase(
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyActivateUseCase(yieldSupplyRepository: YieldSupplyRepository): YieldSupplyActivateUseCase {
|
||||
return YieldSupplyActivateUseCase(
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyDeactivateUseCase(
|
||||
yieldSupplyRepository: YieldSupplyRepository,
|
||||
): YieldSupplyDeactivateUseCase {
|
||||
return YieldSupplyDeactivateUseCase(
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -160,4 +160,5 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
|
|||
Pepecoin, PepecoinTestnet -> null
|
||||
Hyperliquid, HyperliquidTestnet -> null
|
||||
Quai, QuaiTestnet -> null
|
||||
Linea, LineaTestnet -> null
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.routing.utils
|
|||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.feature.qrscanning.QrScanningComponent
|
||||
import com.tangem.feature.referral.api.ReferralComponent
|
||||
|
|
@ -19,6 +20,7 @@ import com.tangem.features.hotwallet.*
|
|||
import com.tangem.features.kyc.KycComponent
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensMode
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||
import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
|
||||
|
|
@ -140,9 +142,15 @@ internal class ChildFactory @Inject constructor(
|
|||
AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES
|
||||
}
|
||||
|
||||
val mode = when (val portfolio = route.portfolioId) {
|
||||
is PortfolioId.Account -> ManageTokensMode.Account(portfolio.accountId)
|
||||
is PortfolioId.Wallet -> ManageTokensMode.Wallet(portfolio.userWalletId)
|
||||
null -> ManageTokensMode.None
|
||||
}
|
||||
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = ManageTokensComponent.Params(route.userWalletId, source),
|
||||
params = ManageTokensComponent.Params(mode, source),
|
||||
componentFactory = manageTokensComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -200,7 +208,7 @@ internal class ChildFactory @Inject constructor(
|
|||
createComponentChild(
|
||||
context = context,
|
||||
params = OnrampComponent.Params(
|
||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
|
||||
userWalletId = route.userWalletId,
|
||||
cryptoCurrency = route.currency,
|
||||
source = route.source,
|
||||
shouldLaunchSepa = route.shouldLaunchSepa,
|
||||
|
|
@ -274,7 +282,7 @@ internal class ChildFactory @Inject constructor(
|
|||
createComponentChild(
|
||||
context = context,
|
||||
params = TokenDetailsComponent.Params(
|
||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.currency,
|
||||
),
|
||||
componentFactory = tokenDetailsComponentFactory,
|
||||
|
|
@ -284,7 +292,7 @@ internal class ChildFactory @Inject constructor(
|
|||
createComponentChild(
|
||||
context = context,
|
||||
params = StakingComponent.Params(
|
||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
|
||||
userWalletId = route.userWalletId,
|
||||
cryptoCurrencyId = route.cryptoCurrencyId,
|
||||
yieldId = route.yieldId,
|
||||
),
|
||||
|
|
@ -297,7 +305,7 @@ internal class ChildFactory @Inject constructor(
|
|||
params = SwapComponent.Params(
|
||||
currencyFrom = route.currencyFrom,
|
||||
currencyTo = route.currencyTo,
|
||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
|
||||
userWalletId = route.userWalletId,
|
||||
isInitialReverseOrder = route.isInitialReverseOrder,
|
||||
screenSource = route.screenSource,
|
||||
),
|
||||
|
|
@ -308,7 +316,7 @@ internal class ChildFactory @Inject constructor(
|
|||
createComponentChild(
|
||||
context = context,
|
||||
params = SendComponent.Params(
|
||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.currency,
|
||||
transactionId = route.transactionId,
|
||||
amount = route.amount,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import com.tangem.core.decompose.navigation.Route
|
|||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.feedback.models.WalletMetaInfo
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
|
@ -51,50 +50,25 @@ sealed class AppRoute(val path: String) : Route {
|
|||
|
||||
@Serializable
|
||||
data class CurrencyDetails(
|
||||
val portfolioId: PortfolioId,
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
) : AppRoute(path = "/currency_details/${portfolioId.stringValue}/${currency.id.value}") {
|
||||
companion object {
|
||||
operator fun invoke(userWalletId: UserWalletId, currency: CryptoCurrency) = CurrencyDetails(
|
||||
portfolioId = PortfolioId(userWalletId),
|
||||
currency = currency,
|
||||
)
|
||||
}
|
||||
}
|
||||
) : AppRoute(path = "/currency_details/${userWalletId.stringValue}/${currency.id.value}")
|
||||
|
||||
@Serializable
|
||||
data class Send(
|
||||
val portfolioId: PortfolioId,
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
val transactionId: String? = null,
|
||||
val amount: String? = null,
|
||||
val tag: String? = null,
|
||||
val destinationAddress: String? = null,
|
||||
) : AppRoute(
|
||||
path = "/send/${portfolioId.stringValue}/${currency.id.value}?" +
|
||||
path = "/send/${userWalletId.stringValue}/${currency.id.value}?" +
|
||||
"&$transactionId" +
|
||||
"&$amount" +
|
||||
"&$tag" +
|
||||
"&$destinationAddress",
|
||||
) {
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
transactionId: String? = null,
|
||||
amount: String? = null,
|
||||
tag: String? = null,
|
||||
destinationAddress: String? = null,
|
||||
) = Send(
|
||||
portfolioId = PortfolioId(userWalletId),
|
||||
currency = currency,
|
||||
transactionId = transactionId,
|
||||
amount = amount,
|
||||
tag = tag,
|
||||
destinationAddress = destinationAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Details(
|
||||
|
|
@ -147,8 +121,8 @@ sealed class AppRoute(val path: String) : Route {
|
|||
@Serializable
|
||||
data class ManageTokens(
|
||||
val source: Source,
|
||||
val userWalletId: UserWalletId? = null,
|
||||
) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/$userWalletId") {
|
||||
val portfolioId: PortfolioId? = null,
|
||||
) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/${portfolioId?.stringValue}") {
|
||||
|
||||
enum class Source {
|
||||
STORIES,
|
||||
|
|
@ -199,51 +173,26 @@ sealed class AppRoute(val path: String) : Route {
|
|||
data class Swap(
|
||||
val currencyFrom: CryptoCurrency,
|
||||
val currencyTo: CryptoCurrency? = null,
|
||||
val portfolioId: PortfolioId,
|
||||
val userWalletId: UserWalletId,
|
||||
val isInitialReverseOrder: Boolean = false,
|
||||
val screenSource: String,
|
||||
) : AppRoute(
|
||||
path = "/swap" +
|
||||
"/${currencyFrom.id.value}" +
|
||||
"/${currencyTo?.id?.value}" +
|
||||
"/${portfolioId.stringValue}" +
|
||||
"/${userWalletId.stringValue}" +
|
||||
"/$isInitialReverseOrder",
|
||||
) {
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currencyFrom: CryptoCurrency,
|
||||
currencyTo: CryptoCurrency? = null,
|
||||
isInitialReverseOrder: Boolean = false,
|
||||
screenSource: String,
|
||||
) = Swap(
|
||||
portfolioId = PortfolioId(userWalletId),
|
||||
currencyFrom = currencyFrom,
|
||||
currencyTo = currencyTo,
|
||||
isInitialReverseOrder = isInitialReverseOrder,
|
||||
screenSource = screenSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data object AppCurrencySelector : AppRoute(path = "/app_currency_selector")
|
||||
|
||||
@Serializable
|
||||
data class Staking(
|
||||
val portfolioId: PortfolioId,
|
||||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrencyId: CryptoCurrency.ID,
|
||||
val yieldId: String,
|
||||
) : AppRoute(path = "/staking/${portfolioId.stringValue}/${cryptoCurrencyId.value}/$yieldId") {
|
||||
companion object {
|
||||
operator fun invoke(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, yieldId: String) =
|
||||
Staking(
|
||||
portfolioId = PortfolioId(userWalletId),
|
||||
cryptoCurrencyId = cryptoCurrencyId,
|
||||
yieldId = yieldId,
|
||||
)
|
||||
}
|
||||
}
|
||||
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/$yieldId")
|
||||
|
||||
@Serializable
|
||||
data class PushNotification(
|
||||
|
|
@ -287,25 +236,11 @@ sealed class AppRoute(val path: String) : Route {
|
|||
@Serializable
|
||||
data class Onramp(
|
||||
val source: OnrampSource,
|
||||
val portfolioId: PortfolioId,
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
val shouldLaunchSepa: Boolean = false,
|
||||
) : AppRoute(path = "/onramp/${portfolioId.stringValue}/${currency.symbol}"), RouteBundleParams {
|
||||
) : AppRoute(path = "/onramp/${userWalletId.stringValue}/${currency.symbol}"), RouteBundleParams {
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
source: OnrampSource,
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
launchSepa: Boolean = false,
|
||||
) = Onramp(
|
||||
source = source,
|
||||
portfolioId = PortfolioId(userWalletId),
|
||||
currency = currency,
|
||||
shouldLaunchSepa = launchSepa,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.common.ui.account
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Displays account name with icon
|
||||
*
|
||||
* @param name account name
|
||||
* @param icon portfolio account icon model
|
||||
* @param iconSize portfolio account icon size
|
||||
* @param nameStyle account name style
|
||||
* @param nameColor account name color
|
||||
* @see AccountIcon
|
||||
*/
|
||||
@Composable
|
||||
fun AccountLabel(
|
||||
name: TextReference,
|
||||
icon: CryptoPortfolioIconUM,
|
||||
iconSize: AccountIconSize,
|
||||
modifier: Modifier = Modifier,
|
||||
nameStyle: TextStyle = TangemTheme.typography.subtitle2,
|
||||
nameColor: Color = TangemTheme.colors.text.tertiary,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
AccountIcon(
|
||||
name = name,
|
||||
icon = icon,
|
||||
size = iconSize,
|
||||
)
|
||||
Text(
|
||||
text = name.resolveReference(),
|
||||
style = nameStyle,
|
||||
color = nameColor,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,21 +13,17 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.preview.AmountScreenClickIntentsStub
|
||||
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||
import com.tangem.common.ui.amountScreen.ui.amountField
|
||||
import com.tangem.common.ui.amountScreen.ui.amountFieldV2
|
||||
import com.tangem.common.ui.amountScreen.ui.buttons
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
/**
|
||||
* Amount screen with field
|
||||
* @param amountState amount state
|
||||
* @param isBalanceHidden flag hidden balances
|
||||
* @param clickIntents amount screen clicks
|
||||
*/
|
||||
@Composable
|
||||
fun AmountScreenContent(
|
||||
amountState: AmountState,
|
||||
isBalanceHidden: Boolean,
|
||||
clickIntents: AmountScreenClickIntents,
|
||||
modifier: Modifier = Modifier,
|
||||
extraContent: (@Composable () -> Unit)? = null,
|
||||
|
|
@ -38,32 +34,17 @@ fun AmountScreenContent(
|
|||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
if (amountState.isRedesignEnabled) {
|
||||
amountFieldV2(
|
||||
amountState = amountState,
|
||||
onValueChange = clickIntents::onAmountValueChange,
|
||||
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
||||
onCurrencyChange = clickIntents::onCurrencyChangeClick,
|
||||
onMaxAmountClick = clickIntents::onMaxValueClick,
|
||||
)
|
||||
if (extraContent != null) {
|
||||
item("EXTRA_CONTENT_KEY") {
|
||||
extraContent()
|
||||
}
|
||||
amountFieldV2(
|
||||
amountState = amountState,
|
||||
onValueChange = clickIntents::onAmountValueChange,
|
||||
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
||||
onCurrencyChange = clickIntents::onCurrencyChangeClick,
|
||||
onMaxAmountClick = clickIntents::onMaxValueClick,
|
||||
)
|
||||
if (extraContent != null) {
|
||||
item("EXTRA_CONTENT_KEY") {
|
||||
extraContent()
|
||||
}
|
||||
} else if (amountState is AmountState.Data) {
|
||||
amountField(
|
||||
amountState = amountState,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
onValueChange = clickIntents::onAmountValueChange,
|
||||
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
||||
)
|
||||
buttons(
|
||||
segmentedButtonConfig = amountState.segmentedButtonConfig,
|
||||
clickIntents = clickIntents,
|
||||
isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled,
|
||||
selectedButton = amountState.selectedButton,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,7 +59,6 @@ private fun SendAmountContentPreview(
|
|||
TangemThemePreview {
|
||||
AmountScreenContent(
|
||||
amountState = amountState,
|
||||
isBalanceHidden = false,
|
||||
clickIntents = AmountScreenClickIntentsStub,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ class AmountCurrencyTransformer(
|
|||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
),
|
||||
selectedButton = prevState.segmentedButtonConfig.indexOfFirst { it.isFiat == value },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,10 +70,9 @@ class AmountReduceByTransformer(
|
|||
error = when {
|
||||
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
|
||||
isLessThanMinimumIfProvided -> {
|
||||
val minimumAmount = minimumTransactionAmount
|
||||
?.amount
|
||||
?.format { crypto(cryptoCurrencyStatus.currency) }
|
||||
.orEmpty()
|
||||
val minimumAmount = minimumTransactionAmount.amount.format {
|
||||
crypto(cryptoCurrencyStatus.currency)
|
||||
}
|
||||
resourceReference(
|
||||
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
|
||||
wrappedList(minimumAmount, minimumAmount),
|
||||
|
|
|
|||
|
|
@ -64,10 +64,9 @@ class AmountReduceToTransformer(
|
|||
error = when {
|
||||
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
|
||||
isLessThanMinimumIfProvided -> {
|
||||
val minimumAmount = minimumTransactionAmount
|
||||
?.amount
|
||||
?.format { crypto(cryptoCurrencyStatus.currency) }
|
||||
.orEmpty()
|
||||
val minimumAmount = minimumTransactionAmount.amount.format {
|
||||
crypto(cryptoCurrencyStatus.currency)
|
||||
}
|
||||
resourceReference(
|
||||
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
|
||||
wrappedList(minimumAmount, minimumAmount),
|
||||
|
|
|
|||
|
|
@ -1,104 +1,35 @@
|
|||
package com.tangem.common.ui.amountScreen.converters
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverter
|
||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverterV2
|
||||
import com.tangem.common.ui.amountScreen.models.AmountParameters
|
||||
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.StringsSigns.DOT
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* Converts initial [String] to [AmountState]
|
||||
*
|
||||
* @property clickIntents amount screen clicks
|
||||
* @property appCurrencyProvider selected app currency provider
|
||||
* @property maxEnterAmount max enter amount data
|
||||
* @property cryptoCurrencyStatusProvider current cryptocurrency status provider
|
||||
* @property iconStateConverter currency icon converter
|
||||
*/
|
||||
@Deprecated("Use AmountStateConverterV2")
|
||||
class AmountStateConverter(
|
||||
private val clickIntents: AmountScreenClickIntents,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val maxEnterAmount: EnterAmountBoundary,
|
||||
private val iconStateConverter: CryptoCurrencyToIconStateConverter,
|
||||
) : Converter<AmountParameters, AmountState> {
|
||||
|
||||
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
AmountFieldConverter(
|
||||
clickIntents = clickIntents,
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
)
|
||||
}
|
||||
|
||||
override fun convert(value: AmountParameters): AmountState {
|
||||
val appCurrency = appCurrencyProvider()
|
||||
val status = cryptoCurrencyStatusProvider()
|
||||
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
val crypto = maxEnterAmount.amount.format { crypto(status.currency) }
|
||||
val hasNoFeeRate = status.value.fiatRate.isNullOrZero()
|
||||
|
||||
return AmountState.Data(
|
||||
title = value.title,
|
||||
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
|
||||
availableBalanceCrypto = stringReference(crypto),
|
||||
availableBalanceFiat = stringReference(fiat),
|
||||
tokenName = stringReference(status.currency.name),
|
||||
tokenIconState = iconStateConverter.convert(status),
|
||||
amountTextField = amountFieldConverter.convert(value.value),
|
||||
isPrimaryButtonEnabled = false,
|
||||
appCurrency = appCurrency,
|
||||
segmentedButtonConfig = persistentListOf(
|
||||
AmountSegmentedButtonsConfig(
|
||||
title = stringReference(status.currency.symbol),
|
||||
iconState = iconStateConverter.convertCustom(
|
||||
value = status,
|
||||
forceGrayscale = hasNoFeeRate,
|
||||
showCustomTokenBadge = false,
|
||||
),
|
||||
isFiat = false,
|
||||
),
|
||||
AmountSegmentedButtonsConfig(
|
||||
title = stringReference(appCurrency.code),
|
||||
iconUrl = appCurrency.iconSmallUrl,
|
||||
isFiat = true,
|
||||
),
|
||||
),
|
||||
isSegmentedButtonsEnabled = !hasNoFeeRate,
|
||||
selectedButton = 0,
|
||||
isRedesignEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts initial [String] to [AmountState]
|
||||
*
|
||||
* @property clickIntents amount screen clicks
|
||||
* @property appCurrency selected app currency
|
||||
* @property maxEnterAmount max enter amount data
|
||||
* @property cryptoCurrencyStatus current cryptocurrency status
|
||||
* @property maxEnterAmount max enter amount data
|
||||
* @property iconStateConverter currency icon converter
|
||||
* @property isBalanceHidden is balance hidden status
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class AmountStateConverterV2(
|
||||
class AmountStateConverter(
|
||||
private val clickIntents: AmountScreenClickIntents,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
|
|
@ -108,7 +39,7 @@ class AmountStateConverterV2(
|
|||
) : Converter<AmountParameters, AmountState> {
|
||||
|
||||
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
AmountFieldConverterV2(
|
||||
AmountFieldConverter(
|
||||
clickIntents = clickIntents,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
appCurrency = appCurrency,
|
||||
|
|
@ -118,19 +49,13 @@ class AmountStateConverterV2(
|
|||
override fun convert(value: AmountParameters): AmountState {
|
||||
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
val crypto = maxEnterAmount.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
||||
val noFeeRate = cryptoCurrencyStatus.value.fiatRate.isNullOrZero()
|
||||
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
||||
return AmountState.Empty(isRedesignEnabled = true)
|
||||
return AmountState.Empty
|
||||
}
|
||||
|
||||
return AmountState.Data(
|
||||
title = value.title,
|
||||
availableBalance = combinedReference(
|
||||
stringReference(crypto),
|
||||
stringReference(" $DOT "),
|
||||
stringReference(fiat),
|
||||
).orMaskWithStars(isBalanceHidden),
|
||||
availableBalanceCrypto = stringReference(crypto).orMaskWithStars(isBalanceHidden),
|
||||
availableBalanceFiat = if (isBalanceHidden) {
|
||||
TextReference.EMPTY
|
||||
|
|
@ -145,25 +70,6 @@ class AmountStateConverterV2(
|
|||
amountTextField = amountFieldConverter.convert(value.value),
|
||||
isPrimaryButtonEnabled = false,
|
||||
appCurrency = appCurrency,
|
||||
segmentedButtonConfig = persistentListOf(
|
||||
AmountSegmentedButtonsConfig(
|
||||
title = stringReference(cryptoCurrencyStatus.currency.symbol),
|
||||
iconState = iconStateConverter.convertCustom(
|
||||
value = cryptoCurrencyStatus,
|
||||
forceGrayscale = noFeeRate,
|
||||
showCustomTokenBadge = false,
|
||||
),
|
||||
isFiat = false,
|
||||
),
|
||||
AmountSegmentedButtonsConfig(
|
||||
title = stringReference(appCurrency.code),
|
||||
iconUrl = appCurrency.iconSmallUrl,
|
||||
isFiat = true,
|
||||
),
|
||||
),
|
||||
isSegmentedButtonsEnabled = !noFeeRate,
|
||||
selectedButton = 0,
|
||||
isRedesignEnabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,10 @@ package com.tangem.common.ui.amountScreen.converters.field
|
|||
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
|
|
@ -32,14 +35,7 @@ class AmountBoundaryUpdateTransformer(
|
|||
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
val crypto = maxEnterAmount.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
||||
|
||||
val availableBalance = combinedReference(
|
||||
stringReference(crypto),
|
||||
stringReference(" $DOT "),
|
||||
stringReference(fiat),
|
||||
)
|
||||
|
||||
return prevState.copy(
|
||||
availableBalance = availableBalance.orMaskWithStars(isBalanceHidden),
|
||||
availableBalanceCrypto = stringReference(crypto).orMaskWithStars(isBalanceHidden),
|
||||
availableBalanceFiat = if (isBalanceHidden) {
|
||||
TextReference.EMPTY
|
||||
|
|
|
|||
|
|
@ -13,75 +13,10 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.tokens.model.AmountType
|
||||
import com.tangem.domain.tokens.model.convertToAmount
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Converts initial [String] to [AmountFieldModel]
|
||||
*
|
||||
* @property clickIntents amount screen clicks
|
||||
* @property appCurrencyProvider selected app currency provider
|
||||
* @property cryptoCurrencyStatusProvider current cryptocurrency status provider
|
||||
*/
|
||||
@Deprecated("Use AmountFieldConverterV2")
|
||||
class AmountFieldConverter(
|
||||
private val clickIntents: AmountScreenClickIntents,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
) : Converter<String, AmountFieldModel> {
|
||||
|
||||
override fun convert(value: String): AmountFieldModel {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val cryptoDecimal = value.toBigDecimalOrNull() ?: BigDecimal.ZERO
|
||||
val cryptoAmount = cryptoDecimal.convertToAmount(cryptoCurrencyStatus.currency)
|
||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate
|
||||
val (fiatValue, fiatDecimal) = when {
|
||||
fiatRate.isNullOrZero() -> "" to null
|
||||
value.isEmpty() -> "" to BigDecimal.ZERO
|
||||
else -> {
|
||||
val fiatDecimal = fiatRate?.multiply(cryptoDecimal)
|
||||
val fiatValue = fiatDecimal?.parseBigDecimal(FIAT_DECIMALS).orEmpty()
|
||||
fiatValue to fiatDecimal
|
||||
}
|
||||
}
|
||||
val isDoneActionEnabled = !cryptoDecimal.isNullOrZero()
|
||||
return AmountFieldModel(
|
||||
value = value,
|
||||
fiatValue = fiatValue,
|
||||
onValueChange = clickIntents::onAmountValueChange,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None,
|
||||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { clickIntents.onAmountNext() },
|
||||
),
|
||||
isFiatValue = false,
|
||||
cryptoAmount = cryptoAmount,
|
||||
fiatAmount = getAppCurrencyAmount(fiatDecimal, appCurrencyProvider()),
|
||||
isError = false,
|
||||
isWarning = false,
|
||||
error = TextReference.EMPTY,
|
||||
isFiatUnavailable = fiatRate == null,
|
||||
isValuePasted = false,
|
||||
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAppCurrencyAmount(fiatValue: BigDecimal?, appCurrency: AppCurrency) = Amount(
|
||||
currencySymbol = appCurrency.symbol,
|
||||
value = fiatValue,
|
||||
decimals = FIAT_DECIMALS,
|
||||
type = AmountType.FiatType(appCurrency.code),
|
||||
)
|
||||
|
||||
private companion object {
|
||||
private const val FIAT_DECIMALS = 2
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts initial [String] to [AmountFieldModel]
|
||||
*
|
||||
|
|
@ -89,7 +24,7 @@ class AmountFieldConverter(
|
|||
* @property appCurrency selected app currency
|
||||
* @property cryptoCurrencyStatus current cryptocurrency status
|
||||
*/
|
||||
class AmountFieldConverterV2(
|
||||
class AmountFieldConverter(
|
||||
private val clickIntents: AmountScreenClickIntents,
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val appCurrency: AppCurrency,
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class AmountFieldSetMaxAmountTransformer(
|
|||
isError = isLessThanMinimumIfProvided,
|
||||
error = when {
|
||||
isLessThanMinimumIfProvided -> {
|
||||
val minimumAmount = minAmount?.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
||||
val minimumAmount = minAmount.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
||||
resourceReference(
|
||||
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
|
||||
wrappedList(minimumAmount, minimumAmount),
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.common.ui.amountScreen.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
/**
|
||||
* Segmented buttons config
|
||||
*
|
||||
* @param title button title
|
||||
* @param iconState currency icon state
|
||||
* @param iconUrl currency icon url
|
||||
* @param isFiat is fiat currency
|
||||
*/
|
||||
@Immutable
|
||||
data class AmountSegmentedButtonsConfig(
|
||||
val title: TextReference,
|
||||
val iconState: CurrencyIconState? = null,
|
||||
val iconUrl: String? = null,
|
||||
val isFiat: Boolean,
|
||||
)
|
||||
|
|
@ -4,7 +4,6 @@ import androidx.compose.runtime.Stable
|
|||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
/** Model for amount state */
|
||||
|
|
@ -12,18 +11,13 @@ import java.math.BigDecimal
|
|||
sealed class AmountState {
|
||||
|
||||
abstract val isPrimaryButtonEnabled: Boolean
|
||||
abstract val isRedesignEnabled: Boolean
|
||||
|
||||
/**
|
||||
* @param isPrimaryButtonEnabled indicates if next state button enabled
|
||||
* @param title title
|
||||
* @param availableBalance user crypto currency balance with fiat balance
|
||||
* @param availableBalanceCrypto user crypto currency balance in crypto
|
||||
* @param availableBalanceFiat user crypto currency balance in fiat
|
||||
* @param tokenIconState crypto currency icon state
|
||||
* @param segmentedButtonConfig currency switcher config
|
||||
* @param selectedButton selected currency index
|
||||
* @param isSegmentedButtonsEnabled indicates if currency switches is enabled
|
||||
* @param amountTextField amount field state
|
||||
* @param appCurrency app currency
|
||||
* @param isEditingDisabled indicated whether amount is editable
|
||||
|
|
@ -32,17 +26,11 @@ sealed class AmountState {
|
|||
*/
|
||||
data class Data(
|
||||
override val isPrimaryButtonEnabled: Boolean,
|
||||
override val isRedesignEnabled: Boolean,
|
||||
val title: TextReference,
|
||||
@Deprecated("Remove with SEND_REDESIGNED toggle")
|
||||
val availableBalance: TextReference,
|
||||
val availableBalanceCrypto: TextReference,
|
||||
val availableBalanceFiat: TextReference,
|
||||
val tokenName: TextReference,
|
||||
val tokenIconState: CurrencyIconState,
|
||||
val segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
|
||||
val selectedButton: Int,
|
||||
val isSegmentedButtonsEnabled: Boolean,
|
||||
val amountTextField: AmountFieldModel,
|
||||
val appCurrency: AppCurrency,
|
||||
val isEditingDisabled: Boolean = false,
|
||||
|
|
@ -50,8 +38,7 @@ sealed class AmountState {
|
|||
val isIgnoreReduce: Boolean = false,
|
||||
) : AmountState()
|
||||
|
||||
data class Empty(
|
||||
override val isPrimaryButtonEnabled: Boolean = false,
|
||||
override val isRedesignEnabled: Boolean,
|
||||
) : AmountState()
|
||||
data object Empty : AmountState() {
|
||||
override val isPrimaryButtonEnabled: Boolean = false
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.common.ui.amountScreen.preview
|
|||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -12,31 +11,18 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.tokens.model.AmountType
|
||||
import com.tangem.utils.StringsSigns
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
|
||||
object AmountStatePreviewData {
|
||||
|
||||
val emptyState = AmountState.Empty(isRedesignEnabled = true)
|
||||
val emptyState = AmountState.Empty
|
||||
|
||||
val amountState = AmountState.Data(
|
||||
isPrimaryButtonEnabled = false,
|
||||
title = stringReference("Family Wallet"),
|
||||
availableBalance = stringReference("2 130,81231238 USDT • 2 129,12 \$)"),
|
||||
availableBalanceCrypto = stringReference("2 130,81231238 USDT"),
|
||||
availableBalanceFiat = stringReference("1 232 129,12 \$"),
|
||||
tokenIconState = CurrencyIconState.Loading,
|
||||
segmentedButtonConfig = persistentListOf(
|
||||
AmountSegmentedButtonsConfig(
|
||||
title = stringReference("USDT"),
|
||||
iconState = CurrencyIconState.Locked,
|
||||
isFiat = false,
|
||||
),
|
||||
AmountSegmentedButtonsConfig(
|
||||
title = stringReference("USD"),
|
||||
isFiat = true,
|
||||
),
|
||||
),
|
||||
appCurrency = AppCurrency.Default,
|
||||
tokenName = stringReference("Tether"),
|
||||
amountTextField = AmountFieldModel(
|
||||
|
|
@ -65,12 +51,9 @@ object AmountStatePreviewData {
|
|||
isValuePasted = false,
|
||||
onValuePastedTriggerDismiss = {},
|
||||
),
|
||||
isSegmentedButtonsEnabled = true,
|
||||
selectedButton = 0,
|
||||
isRedesignEnabled = false,
|
||||
)
|
||||
|
||||
val amountWithValueState = amountState.copy(
|
||||
private val amountWithValueState = amountState.copy(
|
||||
amountTextField = amountState.amountTextField.copy(
|
||||
value = "100.00",
|
||||
cryptoAmount = amountState.amountTextField.cryptoAmount.copy(
|
||||
|
|
@ -84,16 +67,10 @@ object AmountStatePreviewData {
|
|||
)
|
||||
|
||||
val amountStateV2 = amountState.copy(
|
||||
isRedesignEnabled = true,
|
||||
availableBalance = stringReference("2 130,81231238 USDT • 2 129,12 \$)"),
|
||||
availableBalanceCrypto = stringReference("2 130,81231238 USDT"),
|
||||
availableBalanceFiat = stringReference(" ${StringsSigns.DOT} 1 232 129,12 $"),
|
||||
)
|
||||
|
||||
val amountWithValueFiatState = amountWithValueState.copy(
|
||||
amountTextField = amountWithValueState.amountTextField.copy(isFiatValue = false),
|
||||
)
|
||||
|
||||
val amountStateV2WithoutRates = amountState.copy(
|
||||
amountTextField = amountState.amountTextField.copy(
|
||||
fiatAmount = amountState.amountTextField.fiatAmount.copy(
|
||||
|
|
|
|||
|
|
@ -16,10 +16,13 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||
import com.tangem.core.ui.components.ResizableText
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
|
|
@ -59,7 +62,16 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
|
|||
.clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick)
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
CurrencyIcon(state = amountState.tokenIconState)
|
||||
Text(
|
||||
text = amountState.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
SpacerH(20.dp)
|
||||
CurrencyIcon(
|
||||
state = amountState.tokenIconState,
|
||||
iconSize = 40.dp,
|
||||
)
|
||||
ResizableText(
|
||||
text = firstAmount,
|
||||
style = TangemTheme.typography.h2,
|
||||
|
|
|
|||
|
|
@ -1,126 +0,0 @@
|
|||
package com.tangem.common.ui.amountScreen.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
|
||||
import com.tangem.core.ui.components.currency.fiaticon.FiatIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.SendScreenTestTags
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
|
||||
private const val AMOUNT_BUTTONS_KEY = "amountButtonsKey"
|
||||
|
||||
internal fun LazyListScope.buttons(
|
||||
segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
|
||||
clickIntents: AmountScreenClickIntents,
|
||||
isSegmentedButtonsEnabled: Boolean,
|
||||
selectedButton: Int,
|
||||
) {
|
||||
item(
|
||||
key = AMOUNT_BUTTONS_KEY,
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
Row {
|
||||
if (segmentedButtonConfig.isNotEmpty()) {
|
||||
SegmentedButtons(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(TangemTheme.dimens.size40),
|
||||
config = segmentedButtonConfig,
|
||||
showIndication = false,
|
||||
onClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
clickIntents.onCurrencyChangeClick(it.isFiat)
|
||||
},
|
||||
initialSelectedItem = segmentedButtonConfig.getOrNull(selectedButton),
|
||||
isEnabled = isSegmentedButtonsEnabled,
|
||||
) {
|
||||
AmountCurrencyButton(
|
||||
button = it,
|
||||
isSegmentedButtonsEnabled = isSegmentedButtonsEnabled,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
SpacerWMax()
|
||||
}
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.send_max_amount),
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing8)
|
||||
.height(TangemTheme.dimens.size40)
|
||||
.clip(shape = RoundedCornerShape(TangemTheme.dimens.radius26))
|
||||
.background(TangemTheme.colors.button.secondary)
|
||||
.clickable {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
clickIntents.onMaxValueClick()
|
||||
}
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing10,
|
||||
horizontal = TangemTheme.dimens.spacing34,
|
||||
)
|
||||
.testTag(SendScreenTestTags.MAX_BUTTON),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountCurrencyButton(button: AmountSegmentedButtonsConfig, isSegmentedButtonsEnabled: Boolean) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing10,
|
||||
)
|
||||
.testTag(SendScreenTestTags.CURRENCY_BUTTON),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val iconModifier = Modifier
|
||||
.size(TangemTheme.dimens.size18)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing1)
|
||||
if (button.isFiat) {
|
||||
FiatIcon(
|
||||
url = button.iconUrl,
|
||||
size = TangemTheme.dimens.size18,
|
||||
isGrayscale = !isSegmentedButtonsEnabled,
|
||||
modifier = iconModifier.testTag(SendScreenTestTags.FIAT_ICON),
|
||||
)
|
||||
} else if (button.iconState != null) {
|
||||
CurrencyIcon(
|
||||
state = button.iconState,
|
||||
shouldDisplayNetwork = false,
|
||||
modifier = iconModifier.testTag(SendScreenTestTags.CURRENCY_ICON),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = button.title.resolveReference(),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.button,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
package com.tangem.common.ui.amountScreen.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredHeightIn
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment.Companion.BottomCenter
|
||||
import androidx.compose.ui.Alignment.Companion.TopCenter
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.core.ui.components.fields.AmountTextField
|
||||
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.SendScreenTestTags
|
||||
import com.tangem.core.ui.utils.rememberDecimalFormat
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
internal fun AmountField(
|
||||
amountField: AmountFieldModel,
|
||||
appCurrencyCode: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
onValuePastedTriggerDismiss: () -> Unit,
|
||||
) {
|
||||
val decimalFormat = rememberDecimalFormat()
|
||||
val isFiatValue = amountField.isFiatValue
|
||||
val currencyCode = if (isFiatValue) appCurrencyCode else null
|
||||
val (primaryAmount, primaryValue) = if (isFiatValue) {
|
||||
amountField.fiatAmount to amountField.fiatValue
|
||||
} else {
|
||||
amountField.cryptoAmount to amountField.value
|
||||
}
|
||||
val requester = remember { FocusRequester() }
|
||||
val symbolColor = if (primaryValue.isBlank()) TangemTheme.colors.text.disabled else TangemTheme.colors.text.primary1
|
||||
AmountTextField(
|
||||
value = primaryValue,
|
||||
decimals = primaryAmount.decimals,
|
||||
visualTransformation = AmountVisualTransformation(
|
||||
decimals = primaryAmount.decimals,
|
||||
symbol = primaryAmount.currencySymbol,
|
||||
currencyCode = currencyCode,
|
||||
decimalFormat = decimalFormat,
|
||||
symbolColor = symbolColor,
|
||||
),
|
||||
onValueChange = onValueChange,
|
||||
keyboardOptions = amountField.keyboardOptions,
|
||||
keyboardActions = amountField.keyboardActions,
|
||||
textStyle = TangemTheme.typography.h2.copy(
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
),
|
||||
isAutoResize = true,
|
||||
isValuePasted = amountField.isValuePasted,
|
||||
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
|
||||
modifier = Modifier
|
||||
.focusRequester(requester)
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing24,
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
)
|
||||
.requiredHeightIn(min = TangemTheme.dimens.size32),
|
||||
)
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
delay(timeMillis = 200)
|
||||
requester.requestFocus()
|
||||
}
|
||||
|
||||
AmountSecondary(amountField, appCurrencyCode)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountSecondary(amountField: AmountFieldModel, appCurrencyCode: String) {
|
||||
val secondaryAmount = if (amountField.isFiatValue) amountField.cryptoAmount else amountField.fiatAmount
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
) {
|
||||
val text = if (amountField.isFiatValue) {
|
||||
secondaryAmount.value.format { crypto(secondaryAmount.currencySymbol, secondaryAmount.decimals) }
|
||||
} else {
|
||||
secondaryAmount.value.format {
|
||||
fiat(
|
||||
fiatCurrencySymbol = secondaryAmount.currencySymbol,
|
||||
fiatCurrencyCode = appCurrencyCode,
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
style = TangemTheme.typography.caption2.copy(textDirection = TextDirection.ContentOrLtr),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.align(TopCenter)
|
||||
.padding(bottom = TangemTheme.dimens.spacing32)
|
||||
.testTag(SendScreenTestTags.SECONDARY_AMOUNT),
|
||||
)
|
||||
AmountFieldError(
|
||||
isError = amountField.isError,
|
||||
isWarning = amountField.isWarning,
|
||||
error = amountField.error,
|
||||
modifier = Modifier
|
||||
.align(BottomCenter)
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing20,
|
||||
bottom = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountFieldError(
|
||||
isError: Boolean,
|
||||
isWarning: Boolean,
|
||||
error: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = isError || isWarning,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
modifier = modifier,
|
||||
) {
|
||||
val errorText = remember(this, error) { error }
|
||||
val color = if (isError) TangemTheme.colors.text.warning else TangemTheme.colors.text.attention
|
||||
Text(
|
||||
text = errorText.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = color,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,8 +15,6 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
|
|
@ -25,68 +23,12 @@ import com.tangem.core.ui.components.atoms.text.EllipsisText
|
|||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.SendScreenTestTags
|
||||
|
||||
private const val AMOUNT_FIELD_KEY = "amountFieldKey"
|
||||
|
||||
internal fun LazyListScope.amountField(
|
||||
amountState: AmountState.Data,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onValueChange: (String) -> Unit,
|
||||
onValuePastedTriggerDismiss: () -> Unit,
|
||||
) {
|
||||
item(key = AMOUNT_FIELD_KEY) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.background(TangemTheme.colors.background.action),
|
||||
) {
|
||||
Text(
|
||||
text = amountState.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing14)
|
||||
.testTag(SendScreenTestTags.AMOUNT_CONTAINER_TITLE),
|
||||
)
|
||||
|
||||
val balance = amountState.availableBalance.orMaskWithStars(isBalanceHidden).resolveReference()
|
||||
AnimatedContent(
|
||||
targetState = balance,
|
||||
label = "Hide Balance Animation",
|
||||
) {
|
||||
Text(
|
||||
text = it,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing2)
|
||||
.testTag(SendScreenTestTags.AMOUNT_CONTAINER_TEXT),
|
||||
)
|
||||
}
|
||||
CurrencyIcon(
|
||||
state = amountState.tokenIconState,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing32),
|
||||
)
|
||||
AmountField(
|
||||
amountField = amountState.amountTextField,
|
||||
appCurrencyCode = amountState.appCurrency.code,
|
||||
onValueChange = onValueChange,
|
||||
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun LazyListScope.amountFieldV2(
|
||||
amountState: AmountState,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -183,46 +125,49 @@ private fun AmountInfo(amountUM: AmountState, onMaxAmountClick: () -> Unit, modi
|
|||
@Composable
|
||||
private fun AmountInfoMain(amountUM: AmountState, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(
|
||||
targetState = amountUM !is AmountState.Data,
|
||||
targetState = amountUM,
|
||||
modifier = modifier,
|
||||
) { isContent ->
|
||||
if (isContent) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
modifier = Modifier.width(56.dp),
|
||||
)
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography.caption2,
|
||||
modifier = Modifier.width(72.dp),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val amountUM = amountUM as AmountState.Data
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Text(
|
||||
text = amountUM.tokenName.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
)
|
||||
Row {
|
||||
EllipsisText(
|
||||
text = amountUM.availableBalanceCrypto.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
ellipsis = TextEllipsis.OffsetEnd(amountUM.amountTextField.cryptoAmount.currencySymbol.length),
|
||||
modifier = Modifier.weight(1f, fill = false),
|
||||
) { currentAmount ->
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
when (currentAmount) {
|
||||
is AmountState.Data -> {
|
||||
val amountUM = amountUM as AmountState.Data
|
||||
Text(
|
||||
text = amountUM.tokenName.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
)
|
||||
EllipsisText(
|
||||
text = amountUM.availableBalanceFiat.resolveReference(),
|
||||
Row {
|
||||
EllipsisText(
|
||||
text = amountUM.availableBalanceCrypto.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
ellipsis = TextEllipsis.OffsetEnd(
|
||||
amountUM.amountTextField.cryptoAmount.currencySymbol.length,
|
||||
),
|
||||
modifier = Modifier.weight(1f, fill = false),
|
||||
)
|
||||
EllipsisText(
|
||||
text = amountUM.availableBalanceFiat.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
ellipsis = TextEllipsis.OffsetEnd(
|
||||
amountUM.amountTextField.fiatAmount.currencySymbol.length,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
AmountState.Empty -> {
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
modifier = Modifier.width(56.dp),
|
||||
)
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
ellipsis = TextEllipsis.OffsetEnd(amountUM.amountTextField.fiatAmount.currencySymbol.length),
|
||||
modifier = Modifier.width(72.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ fun NavigationButtonsBlock(
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
PreviousButton(state?.prevButton)
|
||||
NavigationPrimaryButton(state?.primaryButton, modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ sealed class NavigationButtonsState {
|
|||
|
||||
data class Data(
|
||||
val primaryButton: NavigationButton?,
|
||||
val prevButton: NavigationButton?,
|
||||
val extraButtons: Pair<NavigationButton, NavigationButton>?,
|
||||
val txUrl: String? = null,
|
||||
val onTextClick: (String) -> Unit,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.common.ui.navigationButtons.preview
|
|||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
||||
internal object NavigationButtonsPreview {
|
||||
|
|
@ -26,16 +25,6 @@ internal object NavigationButtonsPreview {
|
|||
onClick = {},
|
||||
)
|
||||
|
||||
private val prev = NavigationButton(
|
||||
textReference = TextReference.EMPTY,
|
||||
iconRes = R.drawable.ic_back_24,
|
||||
isSecondary = true,
|
||||
isIconVisible = true,
|
||||
shouldShowProgress = false,
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
)
|
||||
|
||||
private val finished = NavigationButton(
|
||||
textReference = resourceReference(R.string.common_close),
|
||||
isSecondary = false,
|
||||
|
|
@ -47,7 +36,6 @@ internal object NavigationButtonsPreview {
|
|||
|
||||
val allButtons = NavigationButtonsState.Data(
|
||||
primaryButton = finished,
|
||||
prevButton = prev,
|
||||
extraButtons = extraButtons,
|
||||
txUrl = "https://tangem.com",
|
||||
onTextClick = {},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import retrofit2.http.Query
|
|||
interface YieldSupplyApi {
|
||||
|
||||
@GET("api/v1/yield/markets")
|
||||
suspend fun getYieldMarkets(@Query("chainId") chainId: Int? = null): ApiResponse<YieldMarketsResponse>
|
||||
suspend fun getYieldMarkets(@Query("chainId") chainId: String? = null): ApiResponse<YieldMarketsResponse>
|
||||
|
||||
@GET("api/v1/yield/token/{chainId}/{tokenAddress}")
|
||||
suspend fun getYieldTokenStatus(
|
||||
|
|
|
|||
|
|
@ -2,8 +2,37 @@ package com.tangem.datasource.api.tangemTech.models.account
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.utils.SerializeNulls
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SaveWalletAccountsResponse(
|
||||
@Json(name = "accounts") val accounts: List<WalletAccountDTO>,
|
||||
)
|
||||
@Json(name = "accounts") val accounts: List<AccountDTO>,
|
||||
) {
|
||||
|
||||
@SerializeNulls
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AccountDTO(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String?,
|
||||
@Json(name = "derivation") val derivationIndex: Int,
|
||||
@Json(name = "icon") val icon: String,
|
||||
@Json(name = "iconColor") val iconColor: String,
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
operator fun invoke(accounts: List<WalletAccountDTO>): SaveWalletAccountsResponse {
|
||||
return SaveWalletAccountsResponse(
|
||||
accounts = accounts.map { accountDto ->
|
||||
AccountDTO(
|
||||
id = accountDto.id,
|
||||
name = accountDto.name,
|
||||
derivationIndex = accountDto.derivationIndex,
|
||||
icon = accountDto.icon,
|
||||
iconColor = accountDto.iconColor,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import com.tangem.common.json.MoshiJsonConverter
|
|||
import com.tangem.datasource.api.common.adapter.*
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.datasource.utils.SerializeNullsFactory
|
||||
import com.tangem.domain.models.scan.serialization.*
|
||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
|
|
@ -28,6 +29,7 @@ class MoshiModule {
|
|||
@NetworkMoshi
|
||||
fun provideNetworkMoshi(): Moshi {
|
||||
return Moshi.Builder()
|
||||
.add(SerializeNullsFactory)
|
||||
.add(
|
||||
PolymorphicJsonAdapterFactory.of(ProviderModel::class.java, "type")
|
||||
.withSubtype(ProviderModel.Public::class.java, "public")
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import android.content.Context
|
|||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse
|
||||
import com.tangem.datasource.local.yieldsupply.DefaultYieldMarketsStore
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.datasource.utils.listTypes
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -34,7 +34,7 @@ object YieldSupplyModule {
|
|||
persistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = listTypes<YieldMarketToken>(),
|
||||
types = listTypes<YieldMarketsResponse.MarketDto>(),
|
||||
defaultValue = emptyList(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "yield_markets_cache") },
|
||||
|
|
|
|||
|
|
@ -12,5 +12,7 @@ interface TangemPayStorage {
|
|||
|
||||
suspend fun getOrderId(customerWalletAddress: String): String?
|
||||
|
||||
suspend fun clear(customerWalletAddress: String)
|
||||
suspend fun clearOrderId(customerWalletAddress: String)
|
||||
|
||||
suspend fun clearAll(customerWalletAddress: String)
|
||||
}
|
||||
|
|
@ -1,21 +1,21 @@
|
|||
package com.tangem.datasource.local.yieldsupply
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
internal class DefaultYieldMarketsStore(
|
||||
private val persistenceStore: DataStore<List<YieldMarketToken>>,
|
||||
private val persistenceStore: DataStore<List<YieldMarketsResponse.MarketDto>>,
|
||||
) : YieldMarketsStore {
|
||||
|
||||
override fun get(): Flow<List<YieldMarketToken>> = persistenceStore.data
|
||||
override fun get(): Flow<List<YieldMarketsResponse.MarketDto>> = persistenceStore.data
|
||||
|
||||
override suspend fun getSyncOrNull(): List<YieldMarketToken>? {
|
||||
override suspend fun getSyncOrNull(): List<YieldMarketsResponse.MarketDto>? {
|
||||
return persistenceStore.data.firstOrNull()
|
||||
}
|
||||
|
||||
override suspend fun store(items: List<YieldMarketToken>) {
|
||||
override suspend fun store(items: List<YieldMarketsResponse.MarketDto>) {
|
||||
persistenceStore.updateData { _ -> items }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tangem.datasource.local.yieldsupply
|
||||
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface YieldMarketsStore {
|
||||
|
||||
fun get(): Flow<List<YieldMarketToken>>
|
||||
fun get(): Flow<List<YieldMarketsResponse.MarketDto>>
|
||||
|
||||
suspend fun getSyncOrNull(): List<YieldMarketToken>?
|
||||
suspend fun getSyncOrNull(): List<YieldMarketsResponse.MarketDto>?
|
||||
|
||||
suspend fun store(items: List<YieldMarketToken>)
|
||||
suspend fun store(items: List<YieldMarketsResponse.MarketDto>)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.datasource.utils
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class SerializeNulls
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.datasource.utils
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* Factory to serialize nulls in Moshi if the class is annotated with [SerializeNulls].
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object SerializeNullsFactory : JsonAdapter.Factory {
|
||||
|
||||
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
|
||||
val rawType = Types.getRawType(type)
|
||||
if (!rawType.isAnnotationPresent(SerializeNulls::class.java)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val nextAdapter: JsonAdapter<Any> = moshi.nextAdapter(this, type, annotations)
|
||||
|
||||
return nextAdapter.serializeNulls()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.datasource.utils
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Moshi
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
// --- DTO ---
|
||||
@SerializeNulls
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UserWithNulls(val id: String?, val name: String?)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UserWithoutNulls(val id: String?, val name: String?)
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class SerializeNullsFactoryTest {
|
||||
|
||||
private val moshi = Moshi.Builder()
|
||||
.add(SerializeNullsFactory)
|
||||
.build()
|
||||
|
||||
@Test
|
||||
fun `should serialize nulls for annotated class`() {
|
||||
val adapter = moshi.adapter(UserWithNulls::class.java)
|
||||
|
||||
val json = adapter.toJson(UserWithNulls(id = null, name = "John"))
|
||||
|
||||
assertThat(json).isEqualTo("""{"id":null,"name":"John"}""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should skip nulls for non-annotated class`() {
|
||||
val adapter = moshi.adapter(UserWithoutNulls::class.java)
|
||||
|
||||
val json = adapter.toJson(UserWithoutNulls(id = null, name = "John"))
|
||||
|
||||
assertThat(json).isEqualTo("""{"name":"John"}""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should deserialize annotated class correctly`() {
|
||||
val adapter = moshi.adapter(UserWithNulls::class.java)
|
||||
|
||||
val json = """{"id":null,"name":"Jane"}"""
|
||||
val result = adapter.fromJson(json)
|
||||
|
||||
assertThat(result).isEqualTo(UserWithNulls(id = null, name = "Jane"))
|
||||
}
|
||||
}
|
||||
|
|
@ -1688,6 +1688,7 @@
|
|||
<string name="yield_module_approve_sheet_title">承認を確定する</string>
|
||||
<string name="yield_module_balance_info_sheet_subtitle">資産残高に関するテキスト [プレースホルダー]</string>
|
||||
<string name="yield_module_balance_info_sheet_title">あなたの%sはAaveに預けられています</string>
|
||||
<string name="yield_module_chart_loading_error">チャートを読み込めません・・</string>
|
||||
<string name="yield_module_deposit_error_notification_title">受け取った金額%1$s %2$sはAaveに入金されませんでした。</string>
|
||||
<string name="yield_module_earn_badge">%1$s%% を獲得</string>
|
||||
<string name="yield_module_earn_sheet_available_title">利用可能</string>
|
||||
|
|
@ -1705,6 +1706,7 @@
|
|||
<string name="yield_module_fee_policy_sheet_max_fee_title">最大手数料</string>
|
||||
<string name="yield_module_fee_policy_sheet_title">手数料ポリシー</string>
|
||||
<string name="yield_module_high_fee_error">ネットワーク手数料が現在高すぎます。設定した上限を下回るまで待機しています。</string>
|
||||
<string name="yield_module_historical_returns">過去のリターン</string>
|
||||
<string name="yield_module_main_view_approve_notification_description">ここに説明を入力してください。1〜3行が理想的です。[プレースホルダー]</string>
|
||||
<string name="yield_module_main_view_approve_notification_title">トークン承認が必要</string>
|
||||
<string name="yield_module_network_fee_unreachable_notification_description">ネットワーク接続を確認してください</string>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
<string name="biometric_unavailable_warning">Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона.</string>
|
||||
<string name="bitcoin_promo_activation_error">При обработке промокода произошла ошибка. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="bitcoin_promo_activation_error_title">Ошибка активации</string>
|
||||
<string name="bitcoin_promo_activation_success">Ваш промокод был успешно активирован. Награда будет зачислена на ваш счёт в течение 14 дней.</string>
|
||||
<string name="bitcoin_promo_activation_success">Ваш промокод успешно активирован. Бонус 10 USDT в Bitcoin будет зачислен через 14 дней.</string>
|
||||
<string name="bitcoin_promo_activation_success_title">Промокод активирован</string>
|
||||
<string name="bitcoin_promo_already_activated">Этот промокод уже был использован и не может быть активирован повторно.</string>
|
||||
<string name="bitcoin_promo_already_activated_title">Код недоступен</string>
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
<string name="common_analytics">Аналитика</string>
|
||||
<string name="common_apply">Применить</string>
|
||||
<string name="common_approval">Одобрение</string>
|
||||
<string name="common_approve">Разрешить</string>
|
||||
<string name="common_approve">Подтвердить</string>
|
||||
<string name="common_attention">Внимание</string>
|
||||
<string name="common_available_networks">Доступные сети</string>
|
||||
<string name="common_balance">Баланс: %s</string>
|
||||
|
|
@ -1202,7 +1202,7 @@
|
|||
<string name="swapping_alert_dex_description_with_slippage">В сумму включена комиссия провайдера сервиса. \n\nПроскальзывание провайдера составляет до %s</string>
|
||||
<string name="swapping_alert_title">Информация</string>
|
||||
<string name="swapping_approve_information_text">Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен.</string>
|
||||
<string name="swapping_approve_information_title">Разрешение</string>
|
||||
<string name="swapping_approve_information_title">Подтвердить</string>
|
||||
<string name="swapping_fee_estimation_error_text">Ошибка расчета комиссии. Пожалуйста, отправьте информацию в поддержку.</string>
|
||||
<string name="swapping_from_title">Вы отправляете</string>
|
||||
<string name="swapping_high_price_impact_description">Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.</string>
|
||||
|
|
@ -1559,7 +1559,6 @@
|
|||
<string name="xtz_withdrawal_message_ignore">Нет, отправить все</string>
|
||||
<string name="xtz_withdrawal_message_reduce">Уменьшить на %s XTZ</string>
|
||||
<string name="xtz_withdrawal_message_warning">Чтобы не платить повышенную комиссию при следующем пополнении кошелька, уменьшите сумму на %s XTZ</string>
|
||||
<string name="yield_module_approve_needed_notification_cta">Выдать разрешение</string>
|
||||
<string name="yield_module_chart_loading_error">Невозможно загрузить график</string>
|
||||
<string name="yield_module_deposit_error_notification_title">Полученная сумма, %1$s %2$s, не была зачислена на Aave.</string>
|
||||
<string name="yield_module_high_fee_error">Сетевая комиссия сейчас слишком высокая. Ожидаем, пока она упадёт ниже вашего лимита.</string>
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@
|
|||
<string name="biometric_unavailable_warning">You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings.</string>
|
||||
<string name="bitcoin_promo_activation_error">An error occurred while processing your promo code. Please try again later.</string>
|
||||
<string name="bitcoin_promo_activation_error_title">Activation error</string>
|
||||
<string name="bitcoin_promo_activation_success">Your promo code was successfully activated. A reward will be credited to your account within 14 days.</string>
|
||||
<string name="bitcoin_promo_activation_success">Your promo code was successfully activated. A bonus of 10 USDT in Bitcoin will be credited to your account within 14 days.</string>
|
||||
<string name="bitcoin_promo_activation_success_title">Promo Code Activated</string>
|
||||
<string name="bitcoin_promo_already_activated">This promo code has already been used and cannot be activated again.</string>
|
||||
<string name="bitcoin_promo_already_activated_title">Code unavailable</string>
|
||||
|
|
@ -1117,8 +1117,8 @@
|
|||
<string name="settings_forget_wallet_footer">This will remove the wallet from the application. The wallet itself can be added again.</string>
|
||||
<string name="settings_wallet_name_title">Name</string>
|
||||
<string name="stake_token_description">Put your token to work</string>
|
||||
<string name="staking_account_initialization_footer">A network fee is a small payment required to process and confirm your transaction on the blockchain.</string>
|
||||
<string name="staking_account_initialization_message">To start staking, your TON account must be activated with a self-transaction of 1 TON. The funds stay in your wallet — this step only enables your account for staking.</string>
|
||||
<string name="staking_account_initialization_footer">A network fee is a small payment to process and confirm your transaction on the blockchain.</string>
|
||||
<string name="staking_account_initialization_message">To start staking, your TON account must be activated with a self-transaction of 1 TON. The funds stay in your wallet — this step only enables your account for staking.</string>
|
||||
<string name="staking_account_initialization_title">Account activation</string>
|
||||
<string name="staking_amount_requirement_error">The amount to stake must be at least %s</string>
|
||||
<string name="staking_amount_tron_integer_error">Staking amount will be rounded to %1$s TRX due to network rules.</string>
|
||||
|
|
@ -1731,20 +1731,13 @@
|
|||
<string name="wc_uri_already_used_title">URI already used</string>
|
||||
<string name="wc_wallet_connect">WalletConnect</string>
|
||||
<string name="wc_warning_transaction">Suspicious transaction</string>
|
||||
<string name="welcome_create_wallet_already_have">Already have Tangem?</string>
|
||||
<string name="welcome_create_wallet_feature_assets">Thousands of assets</string>
|
||||
<string name="welcome_create_wallet_feature_class">Best in class hardware wallet</string>
|
||||
<string name="welcome_create_wallet_feature_delivery">Fast delivery</string>
|
||||
<string name="welcome_create_wallet_feature_one_tap">Start in one tap</string>
|
||||
<string name="welcome_create_wallet_feature_seamless">Seamless and secure</string>
|
||||
<string name="welcome_create_wallet_feature_use">Simple to use</string>
|
||||
<string name="welcome_create_wallet_hardware_description">Create a hardware wallet with Tangem. Slim as a bank card, secure as a bank vault.</string>
|
||||
<string name="welcome_create_wallet_mobile_description">Create or import a software wallet</string>
|
||||
<string name="welcome_create_wallet_mobile_description_full">Create or import a software wallet on your phone </string>
|
||||
<string name="welcome_create_wallet_mobile_title">Start with Mobile Wallet</string>
|
||||
<string name="welcome_create_wallet_other_method">Other method</string>
|
||||
<string name="welcome_create_wallet_use_hardware_description">Use Tangem Hardware Wallet</string>
|
||||
<string name="welcome_create_wallet_use_hardware_title">Learn more & buy</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Discard</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">You have an interrupted backup. Do you want to resume?</string>
|
||||
<string name="welcome_interrupted_backup_alert_resume">Yes, resume</string>
|
||||
|
|
|
|||
|
|
@ -60,10 +60,10 @@ fun TokenListItem(state: TokensListItemUM, isBalanceHidden: Boolean, modifier: M
|
|||
@Composable
|
||||
fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||
if (state.isExpanded) {
|
||||
ExpandedPortfolioHeader(state.state, modifier)
|
||||
ExpandedPortfolioHeader(state = state.tokenItemUM, isCollapsable = state.isCollapsable, modifier = modifier)
|
||||
} else {
|
||||
TokenItem(
|
||||
state = state.state,
|
||||
state = state.tokenItemUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
@ -83,7 +83,7 @@ fun PortfolioTokensListItem(state: PortfolioTokensListItemUM, isBalanceHidden: B
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpandedPortfolioHeader(state: TokenItemState, modifier: Modifier = Modifier) {
|
||||
private fun ExpandedPortfolioHeader(state: TokenItemState, isCollapsable: Boolean, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
|
|
@ -130,11 +130,13 @@ private fun ExpandedPortfolioHeader(state: TokenItemState, modifier: Modifier =
|
|||
)
|
||||
}
|
||||
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size16),
|
||||
painter = painterResource(id = R.drawable.ic_minimize_24),
|
||||
tint = TangemTheme.colors.icon.inactive,
|
||||
contentDescription = null,
|
||||
)
|
||||
if (isCollapsable) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size16),
|
||||
painter = painterResource(id = R.drawable.ic_minimize_24),
|
||||
tint = TangemTheme.colors.icon.inactive,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
|
|||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/** Tokens list item state */
|
||||
@Immutable
|
||||
|
|
@ -41,11 +42,12 @@ sealed interface TokensListItemUM {
|
|||
}
|
||||
|
||||
data class Portfolio(
|
||||
val state: TokenItemState,
|
||||
val tokenItemUM: TokenItemState,
|
||||
val isExpanded: Boolean,
|
||||
val tokens: List<PortfolioTokensListItemUM>,
|
||||
val isCollapsable: Boolean,
|
||||
val tokens: ImmutableList<PortfolioTokensListItemUM>,
|
||||
) : TokensListItemUM {
|
||||
override val id: String = state.id
|
||||
override val id: String = tokenItemUM.id
|
||||
}
|
||||
|
||||
data class Text(override val id: Any, val text: TextReference) : TokensListItemUM
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.core.ui.decompose
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Stable
|
||||
interface ComposableListContentComponent<T> {
|
||||
|
||||
val uiState: StateFlow<T>
|
||||
|
||||
fun LazyListScope.content(uiState: T, modifier: Modifier)
|
||||
|
||||
companion object {
|
||||
val EMPTY = EmptyComposableListContentComponent
|
||||
}
|
||||
}
|
||||
|
||||
object EmptyComposableListContentComponent : ComposableListContentComponent<Unit> {
|
||||
override val uiState: StateFlow<Unit> = MutableStateFlow(Unit)
|
||||
|
||||
override fun LazyListScope.content(uiState: Unit, modifier: Modifier) { /* no-op */
|
||||
}
|
||||
}
|
||||
|
|
@ -96,6 +96,7 @@ fun getActiveIconRes(blockchainId: String): Int {
|
|||
"pepecoin", "pepecoin/test" -> R.drawable.img_pepecoin_22
|
||||
"hyperliquid", "hyperliquid/test" -> R.drawable.img_hyperliquid_22
|
||||
"quai", "quai/test" -> R.drawable.img_quai_22
|
||||
"linea", "linea/test" -> R.drawable.img_linea_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -190,6 +191,7 @@ fun getActiveIconResByCoinId(coinId: String): Int {
|
|||
"pepecoin-network", "pepecoin-network/test" -> R.drawable.img_pepecoin_22
|
||||
"hyperliquid", "hyperliquid/test" -> R.drawable.img_hyperliquid_22
|
||||
"quai", "quai/test" -> R.drawable.img_quai_22
|
||||
"linea", "linea/test" -> R.drawable.img_linea_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -287,6 +289,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int {
|
|||
"pepecoin", "pepecoin/test" -> R.drawable.ic_pepecoin_22
|
||||
"hyperliquid", "hyperliquid/test" -> R.drawable.ic_hyperliquid_22
|
||||
"quai", "quai/test" -> R.drawable.ic_quai_22
|
||||
"linea", "linea/test" -> R.drawable.ic_linea_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -7,4 +7,5 @@ object WalletConnectScreenTestTags {
|
|||
const val APP_NAME = "WALLET_CONNECT_SCREEN_APP_NAME"
|
||||
const val APPROVE_ICON = "WALLET_CONNECT_SCREEN_APPROVE_ICON"
|
||||
const val APP_URL = "WALLET_CONNECT_SCREEN_APP_URL"
|
||||
const val WALLET_CONNECT_IMAGE = "WALLET_CONNECT_SCREEN_WALLET_CONNECT_IMAGE"
|
||||
}
|
||||
15
core/ui/src/main/res/drawable/ic_linea_22.xml
Normal file
15
core/ui/src/main/res/drawable/ic_linea_22.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="22">
|
||||
<group>
|
||||
<clip-path android:pathData="M15.573,6H6V16H15.573V6Z" />
|
||||
<path
|
||||
android:fillColor="#121212"
|
||||
android:pathData="M13.951,16H6V7.623H7.819V14.376H13.951V15.999V16Z" />
|
||||
<path
|
||||
android:fillColor="#121212"
|
||||
android:pathData="M13.95,9.245C14.847,9.245 15.573,8.519 15.573,7.623C15.573,6.727 14.847,6 13.95,6C13.054,6 12.328,6.727 12.328,7.623C12.328,8.519 13.054,9.245 13.95,9.245Z" />
|
||||
</group>
|
||||
</vector>
|
||||
26
core/ui/src/main/res/drawable/img_linea_22.xml
Normal file
26
core/ui/src/main/res/drawable/img_linea_22.xml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="22dp"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="22">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"/>
|
||||
<path
|
||||
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M0,0h22v22h-22z"
|
||||
android:fillColor="#61DFFF"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M15.573,6H6V16H15.573V6Z"/>
|
||||
<path
|
||||
android:pathData="M13.951,16H6V7.623H7.819V14.376H13.951V15.999V16Z"
|
||||
android:fillColor="#121212"/>
|
||||
<path
|
||||
android:pathData="M13.95,9.245C14.847,9.245 15.573,8.519 15.573,7.623C15.573,6.727 14.847,6 13.95,6C13.054,6 12.328,6.727 12.328,7.623C12.328,8.519 13.054,9.245 13.95,9.245Z"
|
||||
android:fillColor="#121212"/>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -24,6 +24,7 @@ dependencies {
|
|||
// region Project - Domain
|
||||
api(projects.domain.account)
|
||||
api(projects.domain.card)
|
||||
api(projects.domain.common)
|
||||
api(projects.domain.models)
|
||||
// endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ internal class AccountListConverter @AssistedInject constructor(
|
|||
|
||||
override fun convert(value: GetWalletAccountsResponse): AccountList {
|
||||
return AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
accounts = value.accounts.map(cryptoPortfolioConverter::convert).toSet(),
|
||||
totalAccounts = value.wallet.totalAccounts,
|
||||
sortType = TokensSortTypeConverter.convert(value.wallet.sort),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.data.account.converter
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
|
@ -21,8 +20,8 @@ internal object SaveWalletAccountsResponseConverter : Converter<AccountList, Sav
|
|||
)
|
||||
}
|
||||
|
||||
private fun toDTO(account: Account.CryptoPortfolio): WalletAccountDTO {
|
||||
return WalletAccountDTO(
|
||||
private fun toDTO(account: Account.CryptoPortfolio): SaveWalletAccountsResponse.AccountDTO {
|
||||
return SaveWalletAccountsResponse.AccountDTO(
|
||||
id = account.accountId.value,
|
||||
name = AccountNameConverter.convert(value = account.accountName),
|
||||
derivationIndex = account.derivationIndex.value,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.account.fetcher
|
|||
|
||||
import com.tangem.data.account.store.AccountsResponseStore
|
||||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
|
||||
import com.tangem.data.account.utils.assignTokens
|
||||
import com.tangem.data.account.utils.toUserTokensResponse
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
|
|
@ -14,6 +15,7 @@ import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.
|
|||
import com.tangem.datasource.api.common.response.ETAG_HEADER
|
||||
import com.tangem.datasource.api.common.response.isNetworkError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
|
|
@ -31,17 +33,20 @@ import javax.inject.Singleton
|
|||
* @property accountsResponseStoreFactory factory to create [AccountsResponseStore]
|
||||
* @property userTokensSaver saves user tokens to the database
|
||||
* @property fetchWalletAccountsErrorHandler handles errors during fetching wallet accounts
|
||||
* @property defaultWalletAccountsResponseFactory creates [GetWalletAccountsResponse] from [UserTokensResponse]
|
||||
* @property eTagsStore store for ETags to manage caching
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Singleton
|
||||
internal class DefaultWalletAccountsFetcher @Inject constructor(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
|
||||
private val userTokensSaver: UserTokensSaver,
|
||||
private val fetchWalletAccountsErrorHandler: FetchWalletAccountsErrorHandler,
|
||||
private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory,
|
||||
private val eTagsStore: ETagsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : WalletAccountsFetcher, WalletAccountsSaver {
|
||||
|
|
@ -49,9 +54,11 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
|
|||
override suspend fun fetch(userWalletId: UserWalletId) {
|
||||
val savedAccountsResponse = getAccountsResponseStore(userWalletId = userWalletId).getSyncOrNull()
|
||||
val accountsResponse = fetchWalletAccounts(userWalletId, savedAccountsResponse)
|
||||
val unassignedTokens = accountsResponse?.unassignedTokens
|
||||
?: return
|
||||
|
||||
if (!unassignedTokens.isNullOrEmpty()) {
|
||||
if (accountsResponse.accounts.isEmpty()) {
|
||||
initializeAccounts(userWalletId, accountsResponse)
|
||||
} else if (accountsResponse.unassignedTokens.isNotEmpty()) {
|
||||
assignTokens(userWalletId, accountsResponse)
|
||||
}
|
||||
}
|
||||
|
|
@ -86,11 +93,7 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
|
|||
tangemTechApi.saveWalletAccounts(
|
||||
walletId = userWalletId.stringValue,
|
||||
eTag = eTag,
|
||||
body = body.copy(
|
||||
accounts = body.accounts.map {
|
||||
it.copy(tokens = null, totalTokens = null, totalNetworks = null)
|
||||
},
|
||||
),
|
||||
body = body,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -135,12 +138,23 @@ internal class DefaultWalletAccountsFetcher @Inject constructor(
|
|||
pushWalletAccounts = ::push,
|
||||
storeWalletAccounts = ::store,
|
||||
)
|
||||
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun initializeAccounts(userWalletId: UserWalletId, accountsResponse: GetWalletAccountsResponse) {
|
||||
val response = defaultWalletAccountsResponseFactory.create(
|
||||
userWalletId = userWalletId,
|
||||
userTokensResponse = UserTokensResponse(
|
||||
group = accountsResponse.wallet.group,
|
||||
sort = accountsResponse.wallet.sort,
|
||||
tokens = accountsResponse.unassignedTokens,
|
||||
),
|
||||
)
|
||||
|
||||
pushAndStore(userWalletId, response)
|
||||
}
|
||||
|
||||
private suspend fun assignTokens(userWalletId: UserWalletId, accountsResponse: GetWalletAccountsResponse) {
|
||||
val accountsResponseWithTokens = accountsResponse.assignTokens(userWalletId)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package com.tangem.data.account.fetcher
|
||||
|
||||
import com.tangem.data.account.converter.CryptoPortfolioConverter
|
||||
import com.tangem.data.account.utils.assignTokens
|
||||
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
|
||||
import com.tangem.data.account.utils.toUserTokensResponse
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseAccountIdEnricher
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.Code
|
||||
|
|
@ -13,10 +11,6 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
|||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -24,12 +18,9 @@ import javax.inject.Inject
|
|||
/**
|
||||
* Handles errors that occur during the fetching of wallet accounts
|
||||
*
|
||||
* @property userTokensSaver saves user tokens to the storage
|
||||
* @property userWalletsStore provides access to user wallet data
|
||||
* @property userTokensResponseStore provides access to user token responses.
|
||||
* @property cryptoPortfolioCF factory for converting crypto portfolios
|
||||
* @property userTokensResponseFactory factory for creating user token responses
|
||||
* @property cardCryptoCurrencyFactory factory for creating default cryptocurrencies for multi-currency wallets
|
||||
* @property userTokensSaver saves user tokens to the storage
|
||||
* @property userTokensResponseStore provides access to user token responses.
|
||||
* @property defaultWalletAccountsResponseFactory creates [GetWalletAccountsResponse] from [UserTokensResponse]
|
||||
*
|
||||
* @see DefaultWalletAccountsFetcher
|
||||
*
|
||||
|
|
@ -37,11 +28,8 @@ import javax.inject.Inject
|
|||
*/
|
||||
internal class FetchWalletAccountsErrorHandler @Inject constructor(
|
||||
private val userTokensSaver: UserTokensSaver,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val userTokensResponseStore: UserTokensResponseStore,
|
||||
private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory,
|
||||
private val userTokensResponseFactory: UserTokensResponseFactory,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -61,20 +49,19 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor(
|
|||
savedAccountsResponse: GetWalletAccountsResponse?,
|
||||
pushWalletAccounts: suspend (userWalletId: UserWalletId, accounts: List<WalletAccountDTO>) -> Unit,
|
||||
storeWalletAccounts: suspend (userWalletId: UserWalletId, response: GetWalletAccountsResponse) -> Unit,
|
||||
) {
|
||||
): GetWalletAccountsResponse? {
|
||||
val isResponseUpToDate = error.isNetworkError(code = Code.NOT_MODIFIED)
|
||||
if (isResponseUpToDate) {
|
||||
Timber.e("ETag is up to date, no need to update accounts for wallet: $userWalletId")
|
||||
return
|
||||
return savedAccountsResponse
|
||||
}
|
||||
|
||||
val (accountDTOs, userTokensResponse) = if (savedAccountsResponse == null) {
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
val response = savedAccountsResponse ?: defaultWalletAccountsResponseFactory.create(
|
||||
userWalletId = userWalletId,
|
||||
userTokensResponse = getFromLegacyStore(userWalletId),
|
||||
)
|
||||
|
||||
createDefaultAccountDTOs(userWallet) to getFromLegacyStore(userWalletId).orDefault(userWallet)
|
||||
} else {
|
||||
savedAccountsResponse.accounts to savedAccountsResponse.toUserTokensResponse()
|
||||
}
|
||||
val (accountDTOs, userTokensResponse) = response.accounts to response.toUserTokensResponse()
|
||||
|
||||
val isNotFoundError = error.isNetworkError(code = Code.NOT_FOUND)
|
||||
if (isNotFoundError) {
|
||||
|
|
@ -82,49 +69,18 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor(
|
|||
userTokensSaver.push(userWalletId = userWalletId, response = userTokensResponse)
|
||||
}
|
||||
|
||||
val response = savedAccountsResponse.orDefault(userWalletId, accountDTOs, userTokensResponse)
|
||||
storeWalletAccounts(userWalletId, response)
|
||||
}
|
||||
|
||||
private fun createDefaultAccountDTOs(userWallet: UserWallet): List<WalletAccountDTO> {
|
||||
val accounts = AccountList.empty(userWallet).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
|
||||
val converter = cryptoPortfolioCF.create(userWallet = userWallet)
|
||||
|
||||
return converter.convertListBack(input = accounts)
|
||||
return response
|
||||
}
|
||||
|
||||
private suspend fun getFromLegacyStore(userWalletId: UserWalletId): UserTokensResponse? {
|
||||
return userTokensResponseStore.getSyncOrNull(userWalletId)
|
||||
?.let {
|
||||
it.copy(
|
||||
tokens = UserTokensResponseAccountIdEnricher(userWalletId = userWalletId, tokens = it.tokens),
|
||||
)
|
||||
}
|
||||
.also { userTokensResponseStore.clear(userWalletId) }
|
||||
}
|
||||
|
||||
private fun UserTokensResponse?.orDefault(userWallet: UserWallet): UserTokensResponse {
|
||||
if (this != null) return this
|
||||
|
||||
return userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet = userWallet),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun GetWalletAccountsResponse?.orDefault(
|
||||
userWalletId: UserWalletId,
|
||||
accountDTOs: List<WalletAccountDTO>,
|
||||
userTokensResponse: UserTokensResponse,
|
||||
): GetWalletAccountsResponse {
|
||||
if (this != null) return this
|
||||
|
||||
return GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = userTokensResponse.group,
|
||||
sort = userTokensResponse.sort,
|
||||
totalAccounts = accountDTOs.size,
|
||||
),
|
||||
accounts = accountDTOs.assignTokens(userWalletId = userWalletId, tokens = userTokensResponse.tokens),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import arrow.core.some
|
|||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.producer.MultiAccountListProducer
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -35,10 +36,11 @@ internal class DefaultMultiAccountListProducer @AssistedInject constructor(
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun produce(): Flow<List<AccountList>> {
|
||||
return userWalletsStore.userWallets
|
||||
.map { it.map(UserWallet::walletId) }
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { userWallets ->
|
||||
.flatMapLatest { ids ->
|
||||
combine(
|
||||
flows = userWallets.map(walletAccountListFlowFactory::create),
|
||||
flows = ids.map(walletAccountListFlowFactory::create),
|
||||
transform = ::listOf,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.data.account.producer
|
|||
|
||||
import arrow.core.Option
|
||||
import arrow.core.none
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -11,16 +10,13 @@ import dagger.assisted.AssistedFactory
|
|||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
|
||||
/**
|
||||
* Default implementation of [SingleAccountListProducer].
|
||||
* Produces a list of [AccountList] for a specific user wallet.
|
||||
*
|
||||
* @property params params containing the user wallet ID
|
||||
* @property userWalletsStore store that provides user wallets
|
||||
* @property walletAccountListFlowFactory builder to create flows of [AccountList] for each wallet
|
||||
* @property dispatchers coroutine dispatchers provider
|
||||
*
|
||||
|
|
@ -28,7 +24,6 @@ import kotlinx.coroutines.flow.mapNotNull
|
|||
*/
|
||||
internal class DefaultSingleAccountListProducer @AssistedInject constructor(
|
||||
@Assisted val params: SingleAccountListProducer.Params,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val walletAccountListFlowFactory: WalletAccountListFlowFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SingleAccountListProducer {
|
||||
|
|
@ -37,11 +32,7 @@ internal class DefaultSingleAccountListProducer @AssistedInject constructor(
|
|||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun produce(): Flow<AccountList> {
|
||||
return userWalletsStore.userWallets
|
||||
.mapNotNull { userWallets ->
|
||||
userWallets.firstOrNull { it.walletId == params.userWalletId }
|
||||
}
|
||||
.flatMapLatest(walletAccountListFlowFactory::create)
|
||||
return walletAccountListFlowFactory.create(userWalletId = params.userWalletId)
|
||||
.flowOn(dispatchers.default)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import com.tangem.data.account.converter.AccountListConverter
|
|||
import com.tangem.data.account.store.AccountsResponseStore
|
||||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -22,12 +24,15 @@ import javax.inject.Inject
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WalletAccountListFlowFactory @Inject constructor(
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
|
||||
private val accountListConverterFactory: AccountListConverter.Factory,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet): Flow<AccountList> {
|
||||
fun create(userWalletId: UserWalletId): Flow<AccountList> {
|
||||
val userWallet = userWalletsStore.getSyncStrict(userWalletId)
|
||||
|
||||
return if (userWallet.isMultiCurrency) {
|
||||
createForMultiWallet(userWallet)
|
||||
} else {
|
||||
|
|
@ -53,6 +58,6 @@ internal class WalletAccountListFlowFactory @Inject constructor(
|
|||
setOf(cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet = userWallet))
|
||||
}
|
||||
|
||||
return AccountList.empty(userWallet = userWallet, cryptoCurrencies = currencies)
|
||||
return AccountList.empty(userWalletId = userWallet.walletId, cryptoCurrencies = currencies)
|
||||
}
|
||||
}
|
||||
|
|
@ -101,12 +101,12 @@ internal class DefaultAccountsCRUDRepository(
|
|||
}
|
||||
|
||||
override suspend fun saveAccounts(accountList: AccountList) {
|
||||
val userWalletId = accountList.userWallet.walletId
|
||||
val userWallet = userWalletsStore.getSyncStrict(accountList.userWalletId)
|
||||
|
||||
val converter = convertersContainer.getWalletAccountsResponseCF.create(userWallet = accountList.userWallet)
|
||||
val converter = convertersContainer.getWalletAccountsResponseCF.create(userWallet = userWallet)
|
||||
val accountsResponse = converter.convert(value = accountList)
|
||||
|
||||
walletAccountsSaver.pushAndStore(userWalletId = userWalletId, response = accountsResponse)
|
||||
walletAccountsSaver.pushAndStore(userWalletId = userWallet.walletId, response = accountsResponse)
|
||||
}
|
||||
|
||||
override suspend fun getTotalAccountsCountSync(userWalletId: UserWalletId): Option<Int> = option {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.data.account.utils
|
||||
|
||||
import com.tangem.data.account.converter.CryptoPortfolioConverter
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Factory to create default [GetWalletAccountsResponse].
|
||||
*
|
||||
* @property userWalletsListRepository repository to get user wallet information
|
||||
* @property cryptoPortfolioCF converter factory to convert crypto portfolio accounts
|
||||
* @property userTokensResponseFactory factory to create [UserTokensResponse]
|
||||
* @property cardCryptoCurrencyFactory factory to get default coins for multi-currency wallet
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultWalletAccountsResponseFactory @Inject constructor(
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory,
|
||||
private val userTokensResponseFactory: UserTokensResponseFactory,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
) {
|
||||
|
||||
suspend fun create(userWalletId: UserWalletId, userTokensResponse: UserTokensResponse?): GetWalletAccountsResponse {
|
||||
val userWallet = userWalletsListRepository.userWalletsSync().firstOrNull { it.walletId == userWalletId }
|
||||
|
||||
val accountDTOs = userWallet?.let(::createDefaultAccountDTOs).orEmpty()
|
||||
val response = userTokensResponse.orDefault(userWallet = userWallet)
|
||||
|
||||
return GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = response.group,
|
||||
sort = response.sort,
|
||||
totalAccounts = accountDTOs.size,
|
||||
),
|
||||
accounts = accountDTOs.assignTokens(userWalletId = userWalletId, tokens = response.tokens),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDefaultAccountDTOs(userWallet: UserWallet): List<WalletAccountDTO> {
|
||||
val accounts = AccountList.empty(userWallet.walletId).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
|
||||
val converter = cryptoPortfolioCF.create(userWallet = userWallet)
|
||||
|
||||
return converter.convertListBack(input = accounts)
|
||||
}
|
||||
|
||||
private fun UserTokensResponse?.orDefault(userWallet: UserWallet?): UserTokensResponse {
|
||||
if (this != null) return this
|
||||
|
||||
return userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = userWallet?.let(cardCryptoCurrencyFactory::createDefaultCoinsForMultiCurrencyWallet).orEmpty(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import com.tangem.domain.models.TokensGroupType
|
|||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
internal fun createWalletAccountDTO(
|
||||
|
|
@ -72,13 +71,13 @@ internal fun createGetWalletAccountsResponse(
|
|||
}
|
||||
|
||||
internal fun createAccountList(
|
||||
userWallet: UserWallet,
|
||||
userWalletId: UserWalletId,
|
||||
sortType: TokensSortType = TokensSortType.BALANCE,
|
||||
groupType: TokensGroupType = TokensGroupType.NETWORK,
|
||||
): AccountList {
|
||||
return AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(createCryptoPortfolio(userWallet.walletId)),
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(createCryptoPortfolio(userWalletId)),
|
||||
totalAccounts = 1,
|
||||
sortType = sortType,
|
||||
groupType = groupType,
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ class AccountListConverterTest {
|
|||
),
|
||||
expected = Result.success(
|
||||
createAccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
sortType = TokensSortType.BALANCE,
|
||||
groupType = TokensGroupType.NETWORK,
|
||||
),
|
||||
|
|
@ -110,7 +110,7 @@ class AccountListConverterTest {
|
|||
),
|
||||
expected = Result.success(
|
||||
createAccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
|
|
@ -124,7 +124,7 @@ class AccountListConverterTest {
|
|||
),
|
||||
expected = Result.success(
|
||||
createAccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class GetWalletAccountsResponseConverterTest {
|
|||
@Test
|
||||
fun `cryptoPortfolioConverter throws exception`() {
|
||||
// Arrange
|
||||
val domain = createAccountList(userWallet = userWallet)
|
||||
val domain = createAccountList(userWalletId = userWallet.walletId)
|
||||
val exception = IllegalStateException("Test exception")
|
||||
|
||||
every { cryptoPortfolioConverter.convertBack(any()) } throws exception
|
||||
|
|
@ -92,7 +92,7 @@ class GetWalletAccountsResponseConverterTest {
|
|||
return listOf(
|
||||
ConvertModel(
|
||||
value = createAccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
sortType = TokensSortType.BALANCE,
|
||||
groupType = TokensGroupType.NETWORK,
|
||||
),
|
||||
|
|
@ -106,7 +106,7 @@ class GetWalletAccountsResponseConverterTest {
|
|||
),
|
||||
ConvertModel(
|
||||
value = createAccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,14 +2,10 @@ package com.tangem.data.account.converter
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
|
|
@ -19,13 +15,11 @@ class SaveWalletAccountsResponseConverterTest {
|
|||
@Test
|
||||
fun convert() {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns UserWalletId("011")
|
||||
}
|
||||
val userWalletId = UserWalletId("011")
|
||||
|
||||
val accountList = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(Account.CryptoPortfolio.createMainAccount(userWalletId = userWallet.walletId)),
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(Account.CryptoPortfolio.createMainAccount(userWalletId = userWalletId)),
|
||||
totalAccounts = 1,
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
|
@ -36,7 +30,7 @@ class SaveWalletAccountsResponseConverterTest {
|
|||
// Assert
|
||||
val expected = SaveWalletAccountsResponse(
|
||||
accounts = listOf(
|
||||
WalletAccountDTO(
|
||||
SaveWalletAccountsResponse.AccountDTO(
|
||||
id = accountList.mainAccount.accountId.value,
|
||||
name = (accountList.mainAccount.accountName as? AccountName.Custom)?.value,
|
||||
derivationIndex = accountList.mainAccount.derivationIndex.value,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.data.account.converter.createGetWalletAccountsResponse
|
|||
import com.tangem.data.account.converter.createWalletAccountDTO
|
||||
import com.tangem.data.account.store.AccountsResponseStore
|
||||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
|
||||
import com.tangem.data.common.cache.etag.ETagsStore
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
|
|
@ -36,6 +37,7 @@ class DefaultWalletAccountsFetcherTest {
|
|||
|
||||
private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true)
|
||||
private val fetchWalletAccountsErrorHandler: FetchWalletAccountsErrorHandler = mockk(relaxUnitFun = true)
|
||||
private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory = mockk()
|
||||
private val eTagsStore: ETagsStore = mockk(relaxUnitFun = true)
|
||||
|
||||
private val fetcher: DefaultWalletAccountsFetcher = DefaultWalletAccountsFetcher(
|
||||
|
|
@ -43,6 +45,7 @@ class DefaultWalletAccountsFetcherTest {
|
|||
accountsResponseStoreFactory = accountsResponseStoreFactory,
|
||||
userTokensSaver = userTokensSaver,
|
||||
fetchWalletAccountsErrorHandler = fetchWalletAccountsErrorHandler,
|
||||
defaultWalletAccountsResponseFactory = defaultWalletAccountsResponseFactory,
|
||||
eTagsStore = eTagsStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
|
@ -205,6 +208,16 @@ class DefaultWalletAccountsFetcherTest {
|
|||
tangemTechApi.getWalletAccounts(walletId = userWalletId.stringValue, eTag = eTag)
|
||||
} returns apiError as ApiResponse<GetWalletAccountsResponse>
|
||||
|
||||
coEvery {
|
||||
fetchWalletAccountsErrorHandler.handle(
|
||||
error = apiError.cause,
|
||||
userWalletId = userWalletId,
|
||||
savedAccountsResponse = null,
|
||||
pushWalletAccounts = any(),
|
||||
storeWalletAccounts = any(),
|
||||
)
|
||||
} returns savedAccountsResponse
|
||||
|
||||
// Act
|
||||
fetcher.fetch(userWalletId)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.data.account.fetcher
|
||||
|
||||
import com.tangem.data.account.converter.CryptoPortfolioConverter
|
||||
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
|
||||
import com.tangem.data.account.utils.toUserTokensResponse
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.Code
|
||||
|
|
@ -11,12 +9,11 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
|||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.*
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
|
@ -29,35 +26,21 @@ import org.junit.jupiter.api.TestInstance
|
|||
class FetchWalletAccountsErrorHandlerTest {
|
||||
|
||||
private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true)
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxUnitFun = true)
|
||||
private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory = mockk()
|
||||
private val cryptoPortfolioConverter = mockk<CryptoPortfolioConverter>()
|
||||
private val userTokensResponseFactory: UserTokensResponseFactory = mockk()
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory = mockk()
|
||||
|
||||
private val handler = FetchWalletAccountsErrorHandler(
|
||||
userTokensSaver = userTokensSaver,
|
||||
userWalletsStore = userWalletsStore,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
cryptoPortfolioCF = cryptoPortfolioCF,
|
||||
userTokensResponseFactory = userTokensResponseFactory,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
defaultWalletAccountsResponseFactory = defaultWalletAccountsResponseFactory,
|
||||
)
|
||||
|
||||
private val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setupEach() {
|
||||
clearMocks(
|
||||
userTokensSaver,
|
||||
userWalletsStore,
|
||||
userTokensResponseStore,
|
||||
cryptoPortfolioCF,
|
||||
cryptoPortfolioConverter,
|
||||
cardCryptoCurrencyFactory,
|
||||
defaultWalletAccountsResponseFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -84,12 +67,8 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
|
||||
// Assert
|
||||
coVerify(inverse = true) {
|
||||
userWalletsStore.getSyncStrict(key = any())
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = any())
|
||||
userTokensResponseFactory.createUserTokensResponse(any(), any(), any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
cryptoPortfolioCF.create(any())
|
||||
cryptoPortfolioConverter.convertListBack(any())
|
||||
defaultWalletAccountsResponseFactory.create(userWalletId = any(), userTokensResponse = any())
|
||||
pushWalletAccounts(any(), any())
|
||||
userTokensSaver.push(userWalletId = any(), response = any())
|
||||
storeWalletAccounts(any(), any())
|
||||
|
|
@ -146,12 +125,8 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
userWalletsStore.getSyncStrict(key = any())
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = any())
|
||||
userTokensResponseFactory.createUserTokensResponse(any(), any(), any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
cryptoPortfolioCF.create(any())
|
||||
cryptoPortfolioConverter.convertListBack(any())
|
||||
defaultWalletAccountsResponseFactory.create(userWalletId = any(), userTokensResponse = any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,9 +135,6 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
// Arrange
|
||||
val error = ApiResponseError.TimeoutException()
|
||||
|
||||
val accounts = AccountList.empty(userWallet).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
|
||||
val accountDTO = WalletAccountDTO(
|
||||
id = "nibh",
|
||||
name = "Michael Dotson",
|
||||
|
|
@ -186,18 +158,10 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
|
||||
val userTokensResponse = savedAccountsResponse.toUserTokensResponse()
|
||||
|
||||
every { userWalletsStore.getSyncStrict(userWalletId) } returns userWallet
|
||||
every { cryptoPortfolioCF.create(userWallet) } returns cryptoPortfolioConverter
|
||||
every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountDTO)
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId) } returns null
|
||||
every {
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = emptyList(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
} returns userTokensResponse
|
||||
every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns emptyList()
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId) } returns userTokensResponse
|
||||
coEvery {
|
||||
defaultWalletAccountsResponseFactory.create(userWalletId, userTokensResponse)
|
||||
} returns savedAccountsResponse
|
||||
|
||||
val pushWalletAccounts: suspend (UserWalletId, List<WalletAccountDTO>) -> Unit = mockk(relaxed = true)
|
||||
val storeWalletAccounts: suspend (UserWalletId, GetWalletAccountsResponse) -> Unit = mockk(relaxed = true)
|
||||
|
|
@ -213,16 +177,8 @@ class FetchWalletAccountsErrorHandlerTest {
|
|||
|
||||
// Assert
|
||||
coVerify {
|
||||
userWalletsStore.getSyncStrict(userWalletId)
|
||||
cryptoPortfolioCF.create(userWallet)
|
||||
cryptoPortfolioConverter.convertListBack(accounts)
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId)
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = emptyList(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet)
|
||||
defaultWalletAccountsResponseFactory.create(userWalletId, userTokensResponse)
|
||||
storeWalletAccounts(userWalletId, any())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ class DefaultMultiAccountListProducerTest {
|
|||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns flowOf(accountList)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList)
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
|
@ -63,7 +63,7 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,11 +73,11 @@ class DefaultMultiAccountListProducerTest {
|
|||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val updatedAccountList = AccountList.empty(userWallet = userWallet, sortType = TokensSortType.NONE)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val updatedAccountList = AccountList.empty(userWalletId = userWalletId, sortType = TokensSortType.NONE)
|
||||
val factoryFlow = MutableStateFlow<AccountList?>(null)
|
||||
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull()
|
||||
|
||||
// Act (first emission)
|
||||
factoryFlow.value = accountList
|
||||
|
|
@ -95,9 +95,9 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,10 +107,10 @@ class DefaultMultiAccountListProducerTest {
|
|||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val factoryFlow = MutableStateFlow<AccountList?>(null)
|
||||
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull()
|
||||
|
||||
// Act (first emission)
|
||||
factoryFlow.value = accountList
|
||||
|
|
@ -128,9 +128,9 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ class DefaultMultiAccountListProducerTest {
|
|||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val exception = RuntimeException("Converter error")
|
||||
every { walletAccountListFlowFactory.create(userWallet) } throws exception
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = producer.produceWithFallback().let(::getEmittedValues)
|
||||
|
|
@ -152,7 +152,7 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +178,7 @@ class DefaultMultiAccountListProducerTest {
|
|||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns emptyFlow()
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns emptyFlow()
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
|
@ -188,7 +188,7 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -203,9 +203,9 @@ class DefaultMultiAccountListProducerTest {
|
|||
val userWalletsFlow = MutableStateFlow(listOf(userWallet, userWallet2))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns flowOf(accountList)
|
||||
every { walletAccountListFlowFactory.create(userWallet2) } returns emptyFlow()
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList)
|
||||
every { walletAccountListFlowFactory.create(userWalletId2) } returns emptyFlow()
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
|
@ -215,8 +215,8 @@ class DefaultMultiAccountListProducerTest {
|
|||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWallet2)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
walletAccountListFlowFactory.create(userWalletId2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.data.account.producer
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.utils.getEmittedValues
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
|
|
@ -11,7 +10,6 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -26,7 +24,6 @@ import org.junit.jupiter.api.TestInstance
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultSingleAccountListProducerTest {
|
||||
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
private val walletAccountListFlowFactory: WalletAccountListFlowFactory = mockk()
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
|
|
@ -36,24 +33,22 @@ class DefaultSingleAccountListProducerTest {
|
|||
|
||||
private val producer = DefaultSingleAccountListProducer(
|
||||
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
||||
userWalletsStore = userWalletsStore,
|
||||
walletAccountListFlowFactory = walletAccountListFlowFactory,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(userWalletsStore, walletAccountListFlowFactory)
|
||||
clearMocks(walletAccountListFlowFactory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun produce() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
MutableStateFlow(listOf(userWallet))
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns flowOf(accountList)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList)
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
|
@ -63,22 +58,18 @@ class DefaultSingleAccountListProducerTest {
|
|||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow will updated if factoryFlow is updated`() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val updatedAccountList = AccountList.empty(userWallet = userWallet, sortType = TokensSortType.NONE)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val updatedAccountList = AccountList.empty(userWalletId = userWalletId, sortType = TokensSortType.NONE)
|
||||
val factoryFlow = MutableStateFlow<AccountList?>(null)
|
||||
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull()
|
||||
|
||||
// Act (first emission)
|
||||
factoryFlow.value = accountList
|
||||
|
|
@ -95,23 +86,18 @@ class DefaultSingleAccountListProducerTest {
|
|||
Truth.assertThat(secondEmission).containsExactly(updatedAccountList)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is filtered the same response`() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val factoryFlow = MutableStateFlow<AccountList?>(null)
|
||||
|
||||
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
|
||||
every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull()
|
||||
|
||||
// Act (first emission)
|
||||
factoryFlow.value = accountList
|
||||
|
|
@ -128,71 +114,8 @@ class DefaultSingleAccountListProducerTest {
|
|||
Truth.assertThat(secondEmission).containsExactly(accountList)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
walletAccountListFlowFactory.create(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is empty if factory throws exception`() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
val exception = RuntimeException("Converter error")
|
||||
every { walletAccountListFlowFactory.create(userWallet) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = producer.produceWithFallback().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEmpty() // no emissions
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
walletAccountListFlowFactory.create(userWallet)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is empty if userWalletsFlow returns empty flow`() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = emptyFlow<List<UserWallet>>()
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEmpty() // no emissions
|
||||
|
||||
coVerify(exactly = 1) { userWalletsStore.userWallets }
|
||||
coVerify(inverse = true) { walletAccountListFlowFactory.create(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is empty if userWalletsFlow doesn't contains userWalletId from params`() = runTest {
|
||||
// Arrange
|
||||
val unknownId = UserWalletId("012")
|
||||
val unknownWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns unknownId
|
||||
}
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(unknownWallet))
|
||||
every { userWalletsStore.userWallets } returns userWalletsFlow
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEmpty() // no emissions
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsStore.userWallets
|
||||
}
|
||||
|
||||
coVerify(inverse = true) { walletAccountListFlowFactory.create(any()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.data.account.store.AccountsResponseStore
|
|||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -28,6 +29,7 @@ import org.junit.jupiter.api.TestInstance
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class WalletAccountListFlowFactoryTest {
|
||||
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
private val accountsResponseStoreFactory: AccountsResponseStoreFactory = mockk()
|
||||
private val accountsResponseStore: AccountsResponseStore = mockk()
|
||||
private val accountsResponseStoreFlow = MutableStateFlow<GetWalletAccountsResponse?>(value = null)
|
||||
|
|
@ -38,6 +40,7 @@ class WalletAccountListFlowFactoryTest {
|
|||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
|
||||
private val factory = WalletAccountListFlowFactory(
|
||||
userWalletsStore = userWalletsStore,
|
||||
accountsResponseStoreFactory = accountsResponseStoreFactory,
|
||||
accountListConverterFactory = accountListConverterFactory,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
|
|
@ -49,6 +52,7 @@ class WalletAccountListFlowFactoryTest {
|
|||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(
|
||||
userWalletsStore,
|
||||
accountsResponseStoreFactory,
|
||||
accountsResponseStore,
|
||||
accountListConverterFactory,
|
||||
|
|
@ -66,17 +70,19 @@ class WalletAccountListFlowFactoryTest {
|
|||
every { this@mockk.isMultiCurrency } returns true
|
||||
}
|
||||
|
||||
every { userWalletsStore.getSyncStrict(userWalletId) } returns userWallet
|
||||
|
||||
val accountsResponse = createGetWalletAccountsResponse(userWalletId)
|
||||
every { accountsResponseStoreFactory.create(userWalletId) } returns accountsResponseStore
|
||||
every { accountsResponseStore.data } returns accountsResponseStoreFlow
|
||||
accountsResponseStoreFlow.value = accountsResponse
|
||||
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
every { accountListConverterFactory.create(userWallet) } returns accountListConverter
|
||||
every { accountListConverter.convert(accountsResponse) } returns accountList
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet).let(::getEmittedValues)
|
||||
val actual = factory.create(userWalletId).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = accountList
|
||||
|
|
@ -99,14 +105,16 @@ class WalletAccountListFlowFactoryTest {
|
|||
fun `create for single wallet`() = runTest {
|
||||
val userWallet = MockUserWalletFactory.create().copy(isMultiCurrency = false)
|
||||
|
||||
every { userWalletsStore.getSyncStrict(userWallet.walletId) } returns userWallet
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum
|
||||
every { cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet) } returns currency
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet).let(::getEmittedValues)
|
||||
val actual = factory.create(userWallet.walletId).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = AccountList.empty(userWallet = userWallet, cryptoCurrencies = setOf(currency))
|
||||
val expected = AccountList.empty(userWalletId = userWallet.walletId, cryptoCurrencies = setOf(currency))
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
|
|
@ -126,16 +134,18 @@ class WalletAccountListFlowFactoryTest {
|
|||
fun `flow is created for single wallet with token`() = runTest {
|
||||
val nodl = MockUserWalletFactory.createSingleWalletWithToken()
|
||||
|
||||
every { userWalletsStore.getSyncStrict(nodl.walletId) } returns nodl
|
||||
|
||||
val currencies = cryptoCurrencyFactory.ethereumAndStellar.toSet()
|
||||
every {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet = nodl)
|
||||
} returns currencies.toList()
|
||||
|
||||
// Act
|
||||
val actual = factory.create(nodl).let(::getEmittedValues)
|
||||
val actual = factory.create(nodl.walletId).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = AccountList.empty(userWallet = nodl, cryptoCurrencies = currencies)
|
||||
val expected = AccountList.empty(userWalletId = nodl.walletId, cryptoCurrencies = currencies)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
|
|
|
|||
|
|
@ -584,7 +584,7 @@ class DefaultAccountsCRUDRepositoryTest {
|
|||
every { this@mockk.walletId } returns userWalletId
|
||||
}
|
||||
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
|
||||
val accountsResponse = mockk<GetWalletAccountsResponse>()
|
||||
accountsResponseStoreFlow.value = accountsResponse
|
||||
|
|
@ -593,6 +593,8 @@ class DefaultAccountsCRUDRepositoryTest {
|
|||
every { this@mockk.convert(accountList) } returns accountsResponse
|
||||
}
|
||||
|
||||
every { userWalletsStore.getSyncStrict(userWalletId) } returns userWallet
|
||||
|
||||
every {
|
||||
convertersContainer.getWalletAccountsResponseCF.create(userWallet = userWallet)
|
||||
} returns converter
|
||||
|
|
@ -617,7 +619,7 @@ class DefaultAccountsCRUDRepositoryTest {
|
|||
every { this@mockk.walletId } returns userWalletId
|
||||
}
|
||||
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
|
||||
val accountsResponse = mockk<GetWalletAccountsResponse>()
|
||||
accountsResponseStoreFlow.value = accountsResponse
|
||||
|
|
@ -626,6 +628,8 @@ class DefaultAccountsCRUDRepositoryTest {
|
|||
every { this@mockk.convert(accountList) } returns accountsResponse
|
||||
}
|
||||
|
||||
every { userWalletsStore.getSyncStrict(userWalletId) } returns userWallet
|
||||
|
||||
every {
|
||||
convertersContainer.getWalletAccountsResponseCF.create(userWallet = userWallet)
|
||||
} returns converter
|
||||
|
|
|
|||
|
|
@ -0,0 +1,240 @@
|
|||
package com.tangem.data.account.utils
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.data.account.converter.CryptoPortfolioConverter
|
||||
import com.tangem.data.account.converter.createWalletAccountDTO
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultWalletAccountsResponseFactoryTest {
|
||||
|
||||
private val userWalletsListRepository = mockk<UserWalletsListRepository>()
|
||||
private val cryptoPortfolioCF = mockk<CryptoPortfolioConverter.Factory>()
|
||||
private val cryptoPortfolioConverter = mockk<CryptoPortfolioConverter>()
|
||||
private val userTokensResponseFactory = mockk<UserTokensResponseFactory>()
|
||||
private val cardCryptoCurrencyFactory = mockk<CardCryptoCurrencyFactory>()
|
||||
|
||||
private val factory = DefaultWalletAccountsResponseFactory(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
cryptoPortfolioCF = cryptoPortfolioCF,
|
||||
userTokensResponseFactory = userTokensResponseFactory,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
)
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
|
||||
@BeforeEach
|
||||
fun setUpEach() {
|
||||
every { cryptoPortfolioCF.create(any()) } returns cryptoPortfolioConverter
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDownEach() {
|
||||
clearMocks(
|
||||
userWalletsListRepository,
|
||||
cryptoPortfolioCF,
|
||||
cryptoPortfolioConverter,
|
||||
userTokensResponseFactory,
|
||||
cardCryptoCurrencyFactory,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns empty accounts when user wallet not found`() = runTest {
|
||||
// Arrange
|
||||
val userTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
|
||||
coEvery { userWalletsListRepository.userWalletsSync() } returns emptyList()
|
||||
every {
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = emptyList(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
} returns userTokensResponse
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId = userWalletId, userTokensResponse = null)
|
||||
|
||||
// Assert
|
||||
val expected = GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
totalAccounts = 0,
|
||||
),
|
||||
accounts = emptyList(),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWalletsSync()
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = emptyList(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns response with default tokens when userTokensResponse is null`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet>(relaxed = true) {
|
||||
every { walletId } returns userWalletId
|
||||
}
|
||||
|
||||
val defaultCoins = listOf(mockk<CryptoCurrency.Coin>())
|
||||
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
|
||||
every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns defaultCoins
|
||||
|
||||
val defaultResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = listOf(mockk(relaxed = true)),
|
||||
)
|
||||
|
||||
every {
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = defaultCoins,
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
} returns defaultResponse
|
||||
|
||||
val accounts = AccountList.empty(userWallet.walletId).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
|
||||
val accountsDTO = createWalletAccountDTO(userWalletId)
|
||||
every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountsDTO)
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId, null)
|
||||
|
||||
// Assert
|
||||
val expected = GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = defaultResponse.group,
|
||||
sort = defaultResponse.sort,
|
||||
totalAccounts = 1,
|
||||
),
|
||||
accounts = listOf(accountsDTO),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWalletsSync()
|
||||
cryptoPortfolioConverter.convertListBack(accounts)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet)
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = defaultCoins,
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns response with default tokens when userTokensResponse is null and no default coins`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet>(relaxed = true) {
|
||||
every { walletId } returns userWalletId
|
||||
}
|
||||
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
|
||||
every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns emptyList()
|
||||
val defaultResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
every {
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = emptyList(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
} returns defaultResponse
|
||||
val accounts = AccountList.empty(userWallet.walletId).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
every { cryptoPortfolioConverter.convertListBack(accounts) } returns emptyList()
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId, null)
|
||||
|
||||
// Assert
|
||||
val expected = GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = defaultResponse.group,
|
||||
sort = defaultResponse.sort,
|
||||
totalAccounts = 0,
|
||||
),
|
||||
accounts = emptyList(),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns response with assigned tokens`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet>(relaxed = true) {
|
||||
every { walletId } returns userWalletId
|
||||
}
|
||||
val assignedTokens = listOf(mockk<CryptoCurrency.Token>(), mockk<CryptoCurrency.Token>())
|
||||
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
|
||||
val userTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = listOf(mockk(relaxed = true)),
|
||||
)
|
||||
every {
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = assignedTokens,
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
} returns userTokensResponse
|
||||
|
||||
val accounts = AccountList.empty(userWallet.walletId).accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
val accountsDTO = createWalletAccountDTO(userWalletId)
|
||||
every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountsDTO)
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId, userTokensResponse)
|
||||
|
||||
// Assert
|
||||
val expected = GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
group = userTokensResponse.group,
|
||||
sort = userTokensResponse.sort,
|
||||
totalAccounts = 1,
|
||||
),
|
||||
accounts = listOf(accountsDTO),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -326,6 +326,7 @@ class NetworkFactory @Inject constructor(
|
|||
Blockchain.Pepecoin, Blockchain.PepecoinTestnet,
|
||||
Blockchain.Hyperliquid, Blockchain.HyperliquidTestnet,
|
||||
Blockchain.Quai, Blockchain.QuaiTestnet,
|
||||
Blockchain.Linea, Blockchain.LineaTestnet,
|
||||
-> Network.TransactionExtrasType.NONE
|
||||
// endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,5 +160,6 @@ public val Blockchain.mercuryoNetwork: String?
|
|||
Blockchain.Pepecoin, Blockchain.PepecoinTestnet -> null
|
||||
Blockchain.Hyperliquid, Blockchain.HyperliquidTestnet -> null
|
||||
Blockchain.Quai, Blockchain.QuaiTestnet -> null
|
||||
Blockchain.Linea, Blockchain.LineaTestnet -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import kotlinx.coroutines.withContext
|
|||
import javax.inject.Inject
|
||||
|
||||
private const val VALID_STATUS = "valid"
|
||||
private const val APPROVED_KYC_STATUS = "APPROVED"
|
||||
private const val TAG = "TangemPay: OnboardingRepository"
|
||||
|
||||
internal class DefaultOnboardingRepository @Inject constructor(
|
||||
|
|
@ -47,13 +48,36 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
|
||||
override suspend fun getMainScreenCustomerInfo(): Either<UniversalError, MainScreenCustomerInfo> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val result = requestHelper.requestWithPersistedToken { authHeader ->
|
||||
tangemPayApi.getCustomerMe(authHeader)
|
||||
}.result
|
||||
val customerWalletAddress = requestHelper.getCustomerWalletAddress()
|
||||
|
||||
val orderStatus = getOrderStatus().getOrNull() ?: error("Order status is null")
|
||||
when (val orderId = tangemPayStorage.getOrderId(customerWalletAddress)) {
|
||||
// If order id wasn't saved -> get customer info
|
||||
null -> {
|
||||
MainScreenCustomerInfo(
|
||||
info = getCustomerInfoWithPersistedToken(),
|
||||
orderStatus = OrderStatus.UNKNOWN,
|
||||
)
|
||||
}
|
||||
// If order id was saved -> check its status
|
||||
else -> {
|
||||
val orderStatus = getOrderStatus(orderId)
|
||||
val customerInfo = when (orderStatus) {
|
||||
// Kyc is passed and user waits for order creation -> no need to get customer info
|
||||
OrderStatus.NEW,
|
||||
OrderStatus.PROCESSING,
|
||||
-> CustomerInfo(productInstance = null, isKycApproved = true, cardInfo = null)
|
||||
|
||||
MainScreenCustomerInfo(info = getCustomerInfo(result), orderStatus = orderStatus)
|
||||
// Order was created/cancelled -> clear order id and get customer info
|
||||
OrderStatus.UNKNOWN,
|
||||
OrderStatus.COMPLETED,
|
||||
OrderStatus.CANCELED,
|
||||
-> getCustomerInfoWithPersistedToken().also {
|
||||
tangemPayStorage.clearOrderId(customerWalletAddress)
|
||||
}
|
||||
}
|
||||
MainScreenCustomerInfo(info = customerInfo, orderStatus = orderStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,27 +108,28 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
}
|
||||
return CustomerInfo(
|
||||
productInstance = response?.productInstance?.let { ProductInstance(id = it.id, status = it.status) },
|
||||
kycStatus = response?.kyc?.status,
|
||||
isKycApproved = response?.kyc?.status == APPROVED_KYC_STATUS,
|
||||
cardInfo = cardInfo,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getOrderStatus(): Either<UniversalError, OrderStatus> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val walletAddress = requestHelper.getCustomerWalletAddress()
|
||||
val orderId: String = tangemPayStorage.getOrderId(walletAddress)
|
||||
?: return@runWithErrorLogs OrderStatus.NOT_ISSUED
|
||||
private suspend fun getOrderStatus(orderId: String): OrderStatus {
|
||||
val result = requestHelper.request { authHeader ->
|
||||
tangemPayApi.getOrder(authHeader, orderId)
|
||||
}.result ?: error("Order result is null")
|
||||
|
||||
val result = requestHelper.request { authHeader ->
|
||||
tangemPayApi.getOrder(authHeader, orderId)
|
||||
}.result ?: error("Order result is null")
|
||||
|
||||
when (result.status) {
|
||||
OrderStatus.NEW.apiName -> OrderStatus.NEW
|
||||
OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING
|
||||
OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED
|
||||
else -> OrderStatus.CANCELED
|
||||
}
|
||||
return when (result.status) {
|
||||
OrderStatus.NEW.apiName -> OrderStatus.NEW
|
||||
OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING
|
||||
OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED
|
||||
else -> OrderStatus.CANCELED
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCustomerInfoWithPersistedToken(): CustomerInfo {
|
||||
val result = requestHelper.requestWithPersistedToken { authHeader ->
|
||||
tangemPayApi.getCustomerMe(authHeader)
|
||||
}.result
|
||||
return getCustomerInfo(result)
|
||||
}
|
||||
}
|
||||
|
|
@ -86,6 +86,6 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
}.result
|
||||
val items = TangemPayTxHistoryItemConverter.convertList(result.transactions).filterNotNull()
|
||||
txHistoryItemsStore.store(key = customerWalletAddress, cursor = cursor ?: INITIAL_CURSOR, value = items)
|
||||
}
|
||||
}.onLeft { error(it.toString()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.data.yield.supply
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.yieldsupply.YieldSupplyProvider
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
|
|
@ -9,8 +11,11 @@ import com.tangem.data.yield.supply.converters.YieldMarketTokenConverter
|
|||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.data.yield.supply.converters.YieldTokenStatusConverter
|
||||
import com.tangem.data.yield.supply.converters.YieldTokenChartConverter
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldSupplyChangeTokenStatusBody
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.yield.supply.YieldSupplyMarketRepository
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketTokenStatus
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData
|
||||
|
|
@ -20,25 +25,29 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.withContext
|
||||
import kotlin.collections.map
|
||||
|
||||
internal class DefaultYieldSupplyMarketRepository(
|
||||
internal class DefaultYieldSupplyRepository(
|
||||
private val yieldSupplyApi: YieldSupplyApi,
|
||||
private val store: YieldMarketsStore,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : YieldSupplyMarketRepository {
|
||||
) : YieldSupplyRepository {
|
||||
|
||||
override suspend fun getCachedMarkets(): List<YieldMarketToken>? = withContext(dispatchers.io) {
|
||||
store.getSyncOrNull()?.enrichNetworkIds()
|
||||
val cache = store.getSyncOrNull().orEmpty()
|
||||
val domain = cache.map(YieldMarketTokenConverter::convert)
|
||||
domain.enrichNetworkIds()
|
||||
}
|
||||
|
||||
override suspend fun updateMarkets(): List<YieldMarketToken> = withContext(dispatchers.io) {
|
||||
val response = yieldSupplyApi.getYieldMarkets().getOrThrow()
|
||||
val chains = Blockchain.yieldSupplySupportedBlockchains().map { it.getChainId() }.joinToString(",")
|
||||
val response = yieldSupplyApi.getYieldMarkets(chainId = chains).getOrThrow()
|
||||
val domain = response.marketDtos.map(YieldMarketTokenConverter::convert)
|
||||
store.store(domain)
|
||||
store.store(response.marketDtos)
|
||||
domain
|
||||
}
|
||||
|
||||
override fun getMarketsFlow(): Flow<List<YieldMarketToken>> = store.get().map {
|
||||
it.enrichNetworkIds()
|
||||
it.map(YieldMarketTokenConverter::convert).enrichNetworkIds()
|
||||
}
|
||||
|
||||
override suspend fun getTokenStatus(cryptoCurrencyToken: CryptoCurrency.Token): YieldMarketTokenStatus {
|
||||
|
|
@ -55,6 +64,41 @@ internal class DefaultYieldSupplyMarketRepository(
|
|||
return YieldTokenChartConverter.convert(response)
|
||||
}
|
||||
|
||||
override suspend fun isYieldSupplySupported(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = cryptoCurrency.network.toBlockchain(),
|
||||
derivationPath = cryptoCurrency.network.derivationPath.value,
|
||||
) ?: error("Wallet manager not found")
|
||||
|
||||
(walletManager as? YieldSupplyProvider)?.isSupported() ?: false
|
||||
}
|
||||
|
||||
override suspend fun activateProtocol(cryptoCurrencyToken: CryptoCurrency.Token): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
val chainId = Blockchain.fromNetworkId(cryptoCurrencyToken.network.backendId)?.getChainId()
|
||||
?: error("Chain id is required for evm's")
|
||||
yieldSupplyApi.activateYieldModule(
|
||||
YieldSupplyChangeTokenStatusBody(
|
||||
tokenAddress = cryptoCurrencyToken.contractAddress,
|
||||
chainId = chainId,
|
||||
),
|
||||
).getOrThrow().isActive
|
||||
}
|
||||
|
||||
override suspend fun deactivateProtocol(cryptoCurrencyToken: CryptoCurrency.Token): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
val chainId = Blockchain.fromNetworkId(cryptoCurrencyToken.network.backendId)?.getChainId()
|
||||
?: error("Chain id is required for evm's")
|
||||
yieldSupplyApi.deactivateYieldModule(
|
||||
YieldSupplyChangeTokenStatusBody(
|
||||
tokenAddress = cryptoCurrencyToken.contractAddress,
|
||||
chainId = chainId,
|
||||
),
|
||||
).getOrThrow().isActive
|
||||
}
|
||||
|
||||
private fun List<YieldMarketToken>.enrichNetworkIds(): List<YieldMarketToken> {
|
||||
val chainIdMap = Blockchain.entries.associate { it.getChainId() to it.toNetworkId() }
|
||||
return this.map { token ->
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.data.yield.supply.di
|
||||
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyMarketRepository
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyRepository
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyErrorResolver
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyTransactionRepository
|
||||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.yield.supply.YieldSupplyMarketRepository
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.yield.supply.YieldSupplyErrorResolver
|
||||
import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -37,12 +37,14 @@ internal object YieldSupplyDataModule {
|
|||
fun provideYieldSupplyMarketRepository(
|
||||
yieldSupplyApi: YieldSupplyApi,
|
||||
store: YieldMarketsStore,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): YieldSupplyMarketRepository {
|
||||
return DefaultYieldSupplyMarketRepository(
|
||||
): YieldSupplyRepository {
|
||||
return DefaultYieldSupplyRepository(
|
||||
yieldSupplyApi = yieldSupplyApi,
|
||||
store = store,
|
||||
dispatchers = dispatchers,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ import com.tangem.domain.models.TokensSortType
|
|||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents a list of accounts associated with a user wallet
|
||||
*
|
||||
* @property userWallet the user wallet associated with the account list
|
||||
* @property userWalletId the user wallet id associated with the account list
|
||||
* @property accounts a set of accounts belonging to the user wallet
|
||||
* @property totalAccounts the total number of accounts
|
||||
*
|
||||
|
|
@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable
|
|||
*/
|
||||
@Serializable
|
||||
data class AccountList private constructor(
|
||||
val userWallet: UserWallet,
|
||||
val userWalletId: UserWalletId,
|
||||
val accounts: Set<Account>,
|
||||
val totalAccounts: Int,
|
||||
val sortType: TokensSortType,
|
||||
|
|
@ -51,7 +51,7 @@ data class AccountList private constructor(
|
|||
val accounts = this.accounts.addOrReplace(other) { it.accountId == other.accountId }
|
||||
|
||||
return invoke(
|
||||
userWallet = this.userWallet,
|
||||
userWalletId = this.userWalletId,
|
||||
accounts = accounts,
|
||||
totalAccounts = this.totalAccounts + if (isNewAccount) 1 else 0,
|
||||
sortType = this.sortType,
|
||||
|
|
@ -73,7 +73,7 @@ data class AccountList private constructor(
|
|||
}
|
||||
|
||||
return invoke(
|
||||
userWallet = this.userWallet,
|
||||
userWalletId = this.userWalletId,
|
||||
accounts = accounts,
|
||||
totalAccounts = this.totalAccounts - if (isExistingAccount) 1 else 0,
|
||||
sortType = this.sortType,
|
||||
|
|
@ -134,12 +134,12 @@ data class AccountList private constructor(
|
|||
* Factory method to create an `AccountList` instance.
|
||||
* Validates the input to ensure the accounts list is not empty and contains exactly one main account.
|
||||
*
|
||||
* @param userWallet the user wallet associated with the account list
|
||||
* @param userWalletId the user wallet id associated with the account list
|
||||
* @param accounts a set of accounts belonging to the user wallet
|
||||
* @param totalAccounts the total number of accounts
|
||||
*/
|
||||
operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
userWalletId: UserWalletId,
|
||||
accounts: Set<Account>,
|
||||
totalAccounts: Int,
|
||||
sortType: TokensSortType = TokensSortType.NONE,
|
||||
|
|
@ -169,7 +169,7 @@ data class AccountList private constructor(
|
|||
}
|
||||
|
||||
AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = accounts,
|
||||
totalAccounts = totalAccounts,
|
||||
sortType = sortType,
|
||||
|
|
@ -180,19 +180,19 @@ data class AccountList private constructor(
|
|||
/**
|
||||
* Factory method to create an empty [AccountList] with a main crypto portfolio account
|
||||
*
|
||||
* @param userWallet the user wallet associated with the account list
|
||||
* @param userWalletId the user wallet id associated with the account list
|
||||
*/
|
||||
fun empty(
|
||||
userWallet: UserWallet,
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: Set<CryptoCurrency> = emptySet(),
|
||||
sortType: TokensSortType = TokensSortType.NONE,
|
||||
groupType: TokensGroupType = TokensGroupType.NONE,
|
||||
): AccountList {
|
||||
return AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(
|
||||
Account.CryptoPortfolio.createMainAccount(
|
||||
userWalletId = userWallet.walletId,
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencies = cryptoCurrencies,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ package com.tangem.domain.account.models
|
|||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents a list of account statuses associated with a user wallet
|
||||
*
|
||||
* @property userWallet the user wallet to which the account statuses belong
|
||||
* @property userWalletId the user wallet id to which the account statuses belong
|
||||
* @property accountStatuses a set of account statuses associated with the user wallet
|
||||
* @property totalAccounts the total number of accounts (including archived ones)
|
||||
* @property totalFiatBalance the total fiat balance across all accounts
|
||||
|
|
@ -18,7 +18,7 @@ import kotlinx.serialization.Serializable
|
|||
*/
|
||||
@Serializable
|
||||
data class AccountStatusList(
|
||||
val userWallet: UserWallet,
|
||||
val userWalletId: UserWalletId,
|
||||
val accountStatuses: Set<AccountStatus>,
|
||||
val totalAccounts: Int,
|
||||
val totalFiatBalance: TotalFiatBalance,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@ import com.tangem.domain.account.utils.createAccounts
|
|||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
|
@ -31,7 +27,7 @@ class AccountListTest {
|
|||
val mainAccount = Account.CryptoPortfolio.createMainAccount(userWalletId = userWalletId)
|
||||
|
||||
val accountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
)
|
||||
|
|
@ -49,13 +45,13 @@ class AccountListTest {
|
|||
fun canAddMoreAccounts() {
|
||||
// Arrange
|
||||
val accountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
userWalletId = userWalletId,
|
||||
accounts = createAccounts(userWalletId = userWalletId, count = 2),
|
||||
totalAccounts = 2,
|
||||
).getOrNull()!!
|
||||
|
||||
val fullAccountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
userWalletId = userWalletId,
|
||||
accounts = createAccounts(userWalletId = userWalletId, count = 20),
|
||||
totalAccounts = 20,
|
||||
).getOrNull()!!
|
||||
|
|
@ -67,16 +63,13 @@ class AccountListTest {
|
|||
|
||||
@Test
|
||||
fun empty() {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet>(relaxed = true)
|
||||
|
||||
// Act
|
||||
val actual = AccountList.empty(userWallet)
|
||||
val actual = AccountList.empty(userWalletId)
|
||||
|
||||
// Assert
|
||||
val expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(Account.CryptoPortfolio.createMainAccount(userWalletId = userWallet.walletId)),
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(Account.CryptoPortfolio.createMainAccount(userWalletId = userWalletId)),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!
|
||||
|
||||
|
|
@ -87,19 +80,12 @@ class AccountListTest {
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Create {
|
||||
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(userWallet)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun invoke(model: CreateTestModel) {
|
||||
// Act
|
||||
val actual = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = model.accounts,
|
||||
totalAccounts = model.accounts.size,
|
||||
)
|
||||
|
|
@ -131,13 +117,13 @@ class AccountListTest {
|
|||
createAccounts(userWalletId = userWalletId, count = 1).let {
|
||||
CreateTestModel(
|
||||
accounts = it,
|
||||
expected = AccountList(userWallet = userWallet, accounts = it, totalAccounts = 1),
|
||||
expected = AccountList(userWalletId = userWalletId, accounts = it, totalAccounts = 1),
|
||||
)
|
||||
},
|
||||
createAccounts(userWalletId = userWalletId, count = 20).let {
|
||||
CreateTestModel(
|
||||
accounts = it,
|
||||
expected = AccountList(userWallet = userWallet, accounts = it, totalAccounts = 20),
|
||||
expected = AccountList(userWalletId = userWalletId, accounts = it, totalAccounts = 20),
|
||||
)
|
||||
},
|
||||
CreateTestModel(
|
||||
|
|
@ -171,8 +157,6 @@ class AccountListTest {
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Plus {
|
||||
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun invoke(model: PlusTestModel) {
|
||||
|
|
@ -191,13 +175,13 @@ class AccountListTest {
|
|||
|
||||
PlusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!,
|
||||
toAdd = newAccount,
|
||||
expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(mainAccount, newAccount),
|
||||
totalAccounts = 2,
|
||||
),
|
||||
|
|
@ -211,13 +195,13 @@ class AccountListTest {
|
|||
|
||||
PlusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!,
|
||||
toAdd = newAccount,
|
||||
expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(newAccount),
|
||||
totalAccounts = 1,
|
||||
),
|
||||
|
|
@ -226,7 +210,7 @@ class AccountListTest {
|
|||
// endregion
|
||||
PlusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = createAccounts(userWalletId = userWalletId, count = 20),
|
||||
totalAccounts = 20,
|
||||
).getOrNull()!!,
|
||||
|
|
@ -246,8 +230,6 @@ class AccountListTest {
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Minus {
|
||||
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun invoke(model: MinusTestModel) {
|
||||
|
|
@ -266,13 +248,13 @@ class AccountListTest {
|
|||
|
||||
MinusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(mainAccount, secondaryAccount),
|
||||
totalAccounts = 2,
|
||||
).getOrNull()!!,
|
||||
toRemove = secondaryAccount,
|
||||
expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
),
|
||||
|
|
@ -286,13 +268,13 @@ class AccountListTest {
|
|||
|
||||
MinusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!,
|
||||
toRemove = notInList,
|
||||
expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
),
|
||||
|
|
@ -305,7 +287,7 @@ class AccountListTest {
|
|||
|
||||
MinusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!,
|
||||
|
|
@ -321,7 +303,7 @@ class AccountListTest {
|
|||
|
||||
MinusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = setOf(mainAccount, secondaryAccount),
|
||||
totalAccounts = 2,
|
||||
).getOrNull()!!,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.account.utils.createAccount
|
|||
import com.tangem.domain.account.utils.createAccounts
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -35,20 +34,16 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
mainAccountTokensMigration = mainAccountTokensMigration,
|
||||
)
|
||||
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository, singleAccountListFetcher, mainAccountTokensMigration, userWallet)
|
||||
|
||||
every { userWallet.walletId } returns userWalletId
|
||||
clearMocks(crudRepository, singleAccountListFetcher, mainAccountTokensMigration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should add new crypto portfolio account to existing list`() = runTest {
|
||||
// Arrange
|
||||
val newAccount = createNewAccount()
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val updatedAccountList = (accountList + newAccount).getOrNull()!!
|
||||
|
||||
coEvery {
|
||||
|
|
@ -152,7 +147,7 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
fun `invoke should return error if account list requirements not met`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accounts = createAccounts(userWalletId = userWalletId, count = 20),
|
||||
totalAccounts = 20,
|
||||
).getOrNull()!!
|
||||
|
|
@ -228,7 +223,7 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
fun `invoke should return error if saveAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val newAccount = createNewAccount()
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val updatedAccountList = (accountList + newAccount).getOrNull()!!
|
||||
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
|
@ -266,7 +261,7 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
fun `invoke should return new account if migrate returns error`() = runTest {
|
||||
// Arrange
|
||||
val newAccount = createNewAccount()
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val updatedAccountList = (accountList + newAccount).getOrNull()!!
|
||||
|
||||
val exception = Exception("Migration error")
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import com.tangem.domain.account.usecase.ArchiveCryptoPortfolioUseCase.Error
|
|||
import com.tangem.domain.account.utils.createAccount
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -24,19 +23,17 @@ class ArchiveCryptoPortfolioUseCaseTest {
|
|||
|
||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val useCase = ArchiveCryptoPortfolioUseCase(crudRepository)
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository, userWallet)
|
||||
every { userWallet.walletId } returns userWalletId
|
||||
clearMocks(crudRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should archive existing crypto portfolio account`() = runTest {
|
||||
// Arrange
|
||||
val account = createAccount(userWalletId)
|
||||
val accountList = (AccountList.empty(userWallet) + account).getOrNull()!!
|
||||
val accountList = (AccountList.empty(userWalletId) + account).getOrNull()!!
|
||||
val accountId = account.accountId
|
||||
|
||||
val updatedAccountList = (accountList - account).getOrNull()!!
|
||||
|
|
@ -103,7 +100,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
|
|||
@Test
|
||||
fun `invoke should return error if account not found`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex(1).getOrNull()!!,
|
||||
|
|
@ -126,7 +123,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
|
|||
fun `invoke should return error if saveAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val account = createAccount(userWalletId)
|
||||
val accountList = (AccountList.empty(userWallet) + account).getOrNull()!!
|
||||
val accountList = (AccountList.empty(userWalletId) + account).getOrNull()!!
|
||||
val accountId = account.accountId
|
||||
|
||||
val updatedAccountList = (accountList - account).getOrNull()!!
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import com.tangem.domain.account.usecase.RecoverCryptoPortfolioUseCase.Error
|
|||
import com.tangem.domain.account.utils.createAccount
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -28,19 +27,17 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
|
||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val useCase = RecoverCryptoPortfolioUseCase(crudRepository)
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository, userWallet)
|
||||
every { userWallet.walletId } returns userWalletId
|
||||
clearMocks(crudRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should recover archived crypto portfolio account`() = runTest {
|
||||
// Arrange
|
||||
val account = createAccount(userWalletId)
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val archivedAccount = ArchivedAccount(
|
||||
accountId = account.accountId,
|
||||
name = account.accountName,
|
||||
|
|
@ -122,7 +119,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
fun `invoke should return error if getArchivedAccount throws exception`() = runTest {
|
||||
// Arrange
|
||||
val account = createAccount(userWalletId)
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
|
|
@ -146,7 +143,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
fun `invoke should return error if getArchivedAccount returns null`() = runTest {
|
||||
// Arrange
|
||||
val account = createAccount(userWalletId)
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns None
|
||||
|
|
@ -169,7 +166,7 @@ class RecoverCryptoPortfolioUseCaseTest {
|
|||
fun `invoke should return error if saveAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val account = createAccount(userWalletId)
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val archivedAccount = ArchivedAccount(
|
||||
accountId = account.accountId,
|
||||
name = account.accountName,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import com.tangem.domain.models.account.AccountId
|
|||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -29,19 +28,15 @@ class UpdateCryptoPortfolioUseCaseTest {
|
|||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val useCase = UpdateCryptoPortfolioUseCase(crudRepository = crudRepository)
|
||||
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository, userWallet)
|
||||
|
||||
every { userWallet.walletId } returns userWalletId
|
||||
clearMocks(crudRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should update crypto portfolio account with new name`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
val accountId = accountList.mainAccount.accountId
|
||||
|
||||
val newAccountName = AccountName("New name").getOrNull()!!
|
||||
|
|
@ -66,7 +61,7 @@ class UpdateCryptoPortfolioUseCaseTest {
|
|||
@Test
|
||||
fun `invoke should update crypto portfolio account with new icon`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
val accountId = accountList.mainAccount.accountId
|
||||
|
||||
val newAccountIcon = CryptoPortfolioIcon.ofCustomAccount(
|
||||
|
|
@ -94,7 +89,7 @@ class UpdateCryptoPortfolioUseCaseTest {
|
|||
@Test
|
||||
fun `invoke should update crypto portfolio account with new name and icon`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
val accountId = accountList.mainAccount.accountId
|
||||
|
||||
val newAccountName = AccountName("New name").getOrNull()!!
|
||||
|
|
@ -123,7 +118,7 @@ class UpdateCryptoPortfolioUseCaseTest {
|
|||
@Test
|
||||
fun `invoke if name and icon are null`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
val accountId = accountList.mainAccount.accountId
|
||||
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId = userWalletId) } returns accountList.toOption()
|
||||
|
|
@ -144,7 +139,7 @@ class UpdateCryptoPortfolioUseCaseTest {
|
|||
@Test
|
||||
fun `invoke if getAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
val accountId = accountList.mainAccount.accountId
|
||||
|
||||
val newAccountName = AccountName("New name").getOrNull()!!
|
||||
|
|
@ -192,7 +187,7 @@ class UpdateCryptoPortfolioUseCaseTest {
|
|||
@Test
|
||||
fun `invoke if getAccounts does not contain accountId`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
val accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex(1).getOrNull()!!,
|
||||
|
|
@ -217,7 +212,7 @@ class UpdateCryptoPortfolioUseCaseTest {
|
|||
@Test
|
||||
fun `invoke if saveAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet = userWallet)
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
val accountId = accountList.mainAccount.accountId
|
||||
|
||||
val newAccountName = AccountName("New name").getOrNull()!!
|
||||
|
|
|
|||
|
|
@ -25,12 +25,15 @@ dependencies {
|
|||
api(projects.domain.staking)
|
||||
api(projects.domain.tokens)
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
implementation(deps.kotlin.datetime)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.timber)
|
||||
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
// region DI
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.account.status.di
|
||||
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyByAddressUseCase
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
|
|
@ -31,7 +32,9 @@ internal object AccountStatusUseCaseModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetAccountCurrencyStatusUseCase(): GetAccountCurrencyStatusUseCase {
|
||||
return GetAccountCurrencyStatusUseCase()
|
||||
fun provideGetAccountCurrencyStatusUseCase(
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
): GetAccountCurrencyStatusUseCase {
|
||||
return GetAccountCurrencyStatusUseCase(singleAccountStatusListSupplier = singleAccountStatusListSupplier)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.account.models.AccountStatusList
|
|||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusesFlowFactory
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
|
|
@ -39,6 +40,7 @@ import java.math.BigDecimal
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class DefaultSingleAccountStatusListProducer @AssistedInject constructor(
|
||||
@Assisted private val params: SingleAccountStatusListProducer.Params,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val cryptoCurrencyStatusesFlowFactory: CryptoCurrencyStatusesFlowFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -58,8 +60,12 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
|||
if (account.cryptoCurrencies.isEmpty()) {
|
||||
createEmptyAccountStatusFlow(account)
|
||||
} else {
|
||||
val userWallet = userWalletsListRepository.userWalletsSync().first {
|
||||
it.walletId == params.userWalletId
|
||||
}
|
||||
|
||||
getAccountStatusFlow(
|
||||
userWallet = accountList.userWallet,
|
||||
userWallet = userWallet,
|
||||
account = account,
|
||||
groupType = accountList.groupType,
|
||||
sortType = accountList.sortType,
|
||||
|
|
@ -71,7 +77,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
|
|||
val balances = accountStatuses.map { it.tokenList.totalFiatBalance }
|
||||
|
||||
AccountStatusList(
|
||||
userWallet = accountList.userWallet,
|
||||
userWalletId = accountList.userWalletId,
|
||||
accountStatuses = accountStatuses.toSet(),
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
totalFiatBalance = TotalFiatBalanceCalculator.calculate(balances),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.tangem.domain.account.status.supplier
|
|||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.core.flow.FlowCachingSupplier
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Supplier that provides a single [AccountStatusList] for a specific user wallet.
|
||||
|
|
@ -12,4 +14,10 @@ import com.tangem.domain.core.flow.FlowCachingSupplier
|
|||
abstract class SingleAccountStatusListSupplier(
|
||||
override val factory: SingleAccountStatusListProducer.Factory,
|
||||
override val keyCreator: (SingleAccountStatusListProducer.Params) -> String,
|
||||
) : FlowCachingSupplier<SingleAccountStatusListProducer, SingleAccountStatusListProducer.Params, AccountStatusList>()
|
||||
) : FlowCachingSupplier<SingleAccountStatusListProducer, SingleAccountStatusListProducer.Params, AccountStatusList>() {
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<AccountStatusList> {
|
||||
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||
return this.invoke(params)
|
||||
}
|
||||
}
|
||||
|
|
@ -117,7 +117,7 @@ class GetAccountCurrencyByAddressUseCase(
|
|||
.firstOrNull()
|
||||
|
||||
return ensureNotNull(result) {
|
||||
"No account found for network: $networkId in walletId: ${accountList.userWallet.walletId}"
|
||||
"No account found for network: $networkId in walletId: ${accountList.userWalletId}"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,24 +2,110 @@ package com.tangem.domain.account.status.usecase
|
|||
|
||||
import arrow.core.Option
|
||||
import arrow.core.none
|
||||
import arrow.core.toOption
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.lib.crypto.derivation.AccountNodeRecognizer
|
||||
|
||||
/**
|
||||
* Use case to retrieve the status of a specific cryptocurrency associated with an account.
|
||||
*
|
||||
* @property singleAccountStatusListSupplier supplier to get the list of account statuses.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
// TODO: Implement [REDACTED_JIRA]
|
||||
class GetAccountCurrencyStatusUseCase {
|
||||
class GetAccountCurrencyStatusUseCase(
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Invokes the use case to get the [AccountCryptoCurrencyStatus] for the given [currencyId].
|
||||
* Invokes the use case to get the status of a specific cryptocurrency for a given user wallet.
|
||||
*
|
||||
* @param currencyId The ID of the cryptocurrency to look up.
|
||||
*
|
||||
* @return An [Option] containing the [AccountCryptoCurrencyStatus] if found,
|
||||
* or [arrow.core.None] if not found or if any validation fails.
|
||||
* @param userWalletId the ID of the user wallet.
|
||||
* @param currency the cryptocurrency for which the status is to be retrieved.
|
||||
* @return an [Option] containing [AccountCryptoCurrencyStatus] if found, otherwise None.
|
||||
*/
|
||||
suspend operator fun invoke(currencyId: CryptoCurrency.ID): Option<AccountCryptoCurrencyStatus> = none()
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): Option<AccountCryptoCurrencyStatus> {
|
||||
return invoke(userWalletId = userWalletId, currencyId = currency.id, network = currency.network)
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the use case to get the status of a specific cryptocurrency by its ID for a given user wallet and network.
|
||||
* If the [network] is null, it searches across all accounts for the cryptocurrency.
|
||||
*
|
||||
* @param userWalletId the ID of the user wallet.
|
||||
* @param currencyId the ID of the cryptocurrency.
|
||||
* @param network the network associated with the cryptocurrency, can be null.
|
||||
* @return an [Option] containing [AccountCryptoCurrencyStatus] if found, otherwise None.
|
||||
*/
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currencyId: CryptoCurrency.ID,
|
||||
network: Network?,
|
||||
): Option<AccountCryptoCurrencyStatus> {
|
||||
val accountStatusList = singleAccountStatusListSupplier.getSyncOrNull(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId),
|
||||
) ?: return none()
|
||||
|
||||
return accountStatusList.getExpectedAccountStatuses(network)
|
||||
.asSequence()
|
||||
.filterIsInstance<AccountStatus.CryptoPortfolio>()
|
||||
.mapNotNull { accountStatus ->
|
||||
val status = accountStatus.flattenCurrencies().firstOrNull { it.currency.id == currencyId }
|
||||
?: return@mapNotNull null
|
||||
|
||||
AccountCryptoCurrencyStatus(account = accountStatus.account, status = status)
|
||||
}
|
||||
.firstOrNull()
|
||||
.toOption()
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the expected account statuses based on the provided [network].
|
||||
* If the [network] is null, all account statuses are returned.
|
||||
* If the network has a specific derivation index, it filters the accounts accordingly.
|
||||
*
|
||||
* @param network the network to filter accounts by, can be null.
|
||||
* @return a set of [AccountStatus] that match the expected criteria.
|
||||
*/
|
||||
private fun AccountStatusList.getExpectedAccountStatuses(network: Network?): Set<AccountStatus> {
|
||||
val possibleAccountIndex = network?.getAccountIndexOrNull()
|
||||
|
||||
return when (possibleAccountIndex) {
|
||||
// currency can be in any account
|
||||
null -> accountStatuses
|
||||
// currency only in the main account
|
||||
DerivationIndex.Main.value -> setOf(mainAccount)
|
||||
// currency only in the account with specific derivation index or in the main account
|
||||
else -> {
|
||||
val accountStatus = accountStatuses.firstOrNull {
|
||||
val cryptoPortfolio = it.account as? Account.CryptoPortfolio ?: return@firstOrNull false
|
||||
|
||||
cryptoPortfolio.derivationIndex.value == possibleAccountIndex
|
||||
}
|
||||
|
||||
setOfNotNull(accountStatus, mainAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Network.getAccountIndexOrNull(): Int? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = rawId) ?: return null
|
||||
val recognizer = AccountNodeRecognizer(blockchain)
|
||||
|
||||
return recognizer.recognize(derivationPath)?.toInt()
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import com.tangem.domain.account.models.AccountStatusList
|
|||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusesFlowFactory
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.StatusSource
|
||||
|
|
@ -37,6 +38,7 @@ import java.math.BigDecimal
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultSingleAccountStatusListProducerTest {
|
||||
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk()
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier = mockk()
|
||||
private val cryptoCurrencyStatusesFlowFactory: CryptoCurrencyStatusesFlowFactory = mockk()
|
||||
|
||||
|
|
@ -47,6 +49,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
|
||||
private val producer = DefaultSingleAccountStatusListProducer(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId),
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
singleAccountListSupplier = singleAccountListSupplier,
|
||||
cryptoCurrencyStatusesFlowFactory = cryptoCurrencyStatusesFlowFactory,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
@ -60,7 +63,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
@Test
|
||||
fun `flow is mapped for user wallet id from params`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId)
|
||||
|
||||
every {
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
|
|
@ -71,7 +74,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
|
||||
// Assert
|
||||
val expected = AccountStatusList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accountStatuses = setOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = accountList.mainAccount,
|
||||
|
|
@ -92,8 +95,8 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
@Test
|
||||
fun `flow will updated if balances are updated`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val updatedAccountList = AccountList.empty(userWallet = userWallet, sortType = TokensSortType.BALANCE)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val updatedAccountList = AccountList.empty(userWalletId = userWalletId, sortType = TokensSortType.BALANCE)
|
||||
|
||||
val accountListFlow = MutableStateFlow(value = accountList)
|
||||
|
||||
|
|
@ -106,7 +109,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
|
||||
// Assert (first emission)
|
||||
val expected = AccountStatusList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accountStatuses = setOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = accountList.mainAccount,
|
||||
|
|
@ -125,7 +128,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
|
||||
// Assert (second emission)
|
||||
val expected2 = AccountStatusList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accountStatuses = setOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = updatedAccountList.mainAccount,
|
||||
|
|
@ -147,7 +150,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
@Test
|
||||
fun `flow is filtered the same balance`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
val accountListFlow = MutableStateFlow(value = accountList)
|
||||
|
||||
every {
|
||||
|
|
@ -155,7 +158,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
} returns accountListFlow
|
||||
|
||||
val expected = AccountStatusList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accountStatuses = setOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = accountList.mainAccount,
|
||||
|
|
@ -191,10 +194,12 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
// Arrange
|
||||
val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
val accountList = AccountList.empty(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencies = cryptoCurrencyFactory.ethereumAndStellar.toSet(),
|
||||
)
|
||||
|
||||
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
|
||||
|
||||
every {
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
} returns flowOf(accountList)
|
||||
|
|
@ -220,7 +225,7 @@ class DefaultSingleAccountStatusListProducerTest {
|
|||
|
||||
// Assert
|
||||
val expected = AccountStatusList(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWalletId,
|
||||
accountStatuses = setOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = accountList.mainAccount,
|
||||
|
|
@ -218,7 +218,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
},
|
||||
value = NetworkStatus.Unreachable(address = validNetworkAddress),
|
||||
)
|
||||
val accountList = AccountList.empty(multiUserWallet)
|
||||
val accountList = AccountList.empty(userWalletId)
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(multiUserWallet))
|
||||
coEvery {
|
||||
|
|
@ -253,7 +253,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
|||
network = currency.network,
|
||||
value = NetworkStatus.Unreachable(address = validNetworkAddress),
|
||||
)
|
||||
val accountList = AccountList.empty(userWallet = multiUserWallet, cryptoCurrencies = setOf(currency))
|
||||
val accountList = AccountList.empty(userWalletId = userWalletId, cryptoCurrencies = setOf(currency))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(multiUserWallet))
|
||||
coEvery {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
package com.tangem.domain.account.status.usecase
|
||||
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.common.test.utils.assertNone
|
||||
import com.tangem.common.test.utils.assertSome
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class GetAccountCurrencyStatusUseCaseTest {
|
||||
|
||||
private val supplier = mockk<SingleAccountStatusListSupplier>()
|
||||
private val useCase = GetAccountCurrencyStatusUseCase(singleAccountStatusListSupplier = supplier)
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val supplierParams = SingleAccountStatusListProducer.Params(userWalletId)
|
||||
private val currency = MockCryptoCurrencyFactory().ethereum.let {
|
||||
val derivationPath = Network.DerivationPath.Card("m/44'/60'/0'/0/1")
|
||||
|
||||
it.copy(
|
||||
network = it.network.copy(
|
||||
id = Network.ID(value = "ethereum", derivationPath = derivationPath),
|
||||
derivationPath = derivationPath,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
clearMocks(supplier)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns None when supplier returns null`() = runTest {
|
||||
// Arrange
|
||||
coEvery { supplier.getSyncOrNull(supplierParams) } returns null
|
||||
|
||||
// Act
|
||||
val actual = useCase(userWalletId = userWalletId, currencyId = currency.id, network = null)
|
||||
|
||||
// Assert
|
||||
assertNone(actual)
|
||||
coVerifyOrder { supplier.getSyncOrNull(supplierParams) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns None when AccountList does not contain required currency id`() = runTest {
|
||||
// Arrange
|
||||
val accountStatus = AccountStatus.CryptoPortfolio(
|
||||
account = Account.CryptoPortfolio.createMainAccount(userWalletId),
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = lceLoading(),
|
||||
)
|
||||
|
||||
val accountStatusList = mockk<AccountStatusList>(relaxed = true) {
|
||||
every { this@mockk.accountStatuses } returns setOf(accountStatus)
|
||||
}
|
||||
|
||||
coEvery { supplier.getSyncOrNull(supplierParams) } returns accountStatusList
|
||||
|
||||
// Act
|
||||
val actual = useCase(userWalletId = userWalletId, currencyId = currency.id, network = null)
|
||||
|
||||
// Assert
|
||||
assertNone(actual)
|
||||
coVerifyOrder { supplier.getSyncOrNull(supplierParams) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns Some if network is not null`() = runTest {
|
||||
// Arrange
|
||||
val mainAccountStatus = AccountStatus.CryptoPortfolio(
|
||||
account = Account.CryptoPortfolio.createMainAccount(userWalletId),
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = lceLoading(),
|
||||
)
|
||||
|
||||
val account = mockk<Account.CryptoPortfolio>(relaxed = true) {
|
||||
every { this@mockk.derivationIndex } returns DerivationIndex(1).getOrNull()!!
|
||||
every { this@mockk.cryptoCurrencies } returns setOf(currency)
|
||||
}
|
||||
val currencyStatus = CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading)
|
||||
val accountStatus = AccountStatus.CryptoPortfolio(
|
||||
account = account,
|
||||
tokenList = TokenList.Ungrouped(
|
||||
totalFiatBalance = TotalFiatBalance.Loading,
|
||||
sortedBy = TokensSortType.NONE,
|
||||
currencies = listOf(currencyStatus),
|
||||
),
|
||||
priceChangeLce = lceLoading(),
|
||||
)
|
||||
|
||||
val accountStatusList = mockk<AccountStatusList>(relaxed = true) {
|
||||
every { this@mockk.accountStatuses } returns setOf(mainAccountStatus, accountStatus, mockk())
|
||||
}
|
||||
|
||||
coEvery { supplier.getSyncOrNull(supplierParams) } returns accountStatusList
|
||||
|
||||
// Act
|
||||
val actual = useCase(userWalletId = userWalletId, currencyId = currency.id, network = currency.network)
|
||||
|
||||
// Assert
|
||||
val expected = AccountCryptoCurrencyStatus(account = accountStatus.account, status = currencyStatus)
|
||||
assertSome(actual, expected)
|
||||
|
||||
coVerifyOrder { supplier.getSyncOrNull(supplierParams) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns Some if network is null`() = runTest {
|
||||
// Arrange
|
||||
val account = mockk<Account.CryptoPortfolio>(relaxed = true) {
|
||||
every { this@mockk.cryptoCurrencies } returns setOf(currency)
|
||||
}
|
||||
val currencyStatus = CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading)
|
||||
val accountStatus = AccountStatus.CryptoPortfolio(
|
||||
account = account,
|
||||
tokenList = TokenList.Ungrouped(
|
||||
totalFiatBalance = TotalFiatBalance.Loading,
|
||||
sortedBy = TokensSortType.NONE,
|
||||
currencies = listOf(currencyStatus),
|
||||
),
|
||||
priceChangeLce = lceLoading(),
|
||||
)
|
||||
|
||||
val accountStatusList = mockk<AccountStatusList>(relaxed = true) {
|
||||
every { this@mockk.accountStatuses } returns setOf(accountStatus)
|
||||
}
|
||||
|
||||
coEvery { supplier.getSyncOrNull(supplierParams) } returns accountStatusList
|
||||
|
||||
// Act
|
||||
val actual = useCase(userWalletId = userWalletId, currencyId = currency.id, network = null)
|
||||
|
||||
// Assert
|
||||
val expected = AccountCryptoCurrencyStatus(account = accountStatus.account, status = currencyStatus)
|
||||
assertSome(actual, expected)
|
||||
coVerifyOrder { supplier.getSyncOrNull(supplierParams) }
|
||||
}
|
||||
}
|
||||
|
|
@ -212,6 +212,8 @@ data object Wallet2CardConfig : CardConfig {
|
|||
Blockchain.HyperliquidTestnet -> EllipticCurve.Secp256k1
|
||||
Blockchain.Quai -> EllipticCurve.Secp256k1
|
||||
Blockchain.QuaiTestnet -> EllipticCurve.Secp256k1
|
||||
Blockchain.Linea -> EllipticCurve.Secp256k1
|
||||
Blockchain.LineaTestnet -> EllipticCurve.Secp256k1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -168,6 +168,8 @@ class Wallet2CardConfigTest {
|
|||
Blockchain.HyperliquidTestnet to EllipticCurve.Secp256k1,
|
||||
Blockchain.Quai to EllipticCurve.Secp256k1,
|
||||
Blockchain.QuaiTestnet to EllipticCurve.Secp256k1,
|
||||
Blockchain.Linea to EllipticCurve.Secp256k1,
|
||||
Blockchain.LineaTestnet to EllipticCurve.Secp256k1,
|
||||
)
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed interface TokensAction : Action {
|
||||
|
||||
/** Single way to pass data to the screen */
|
||||
sealed interface SetArgs : TokensAction {
|
||||
object ManageAccess : SetArgs
|
||||
object ReadAccess : SetArgs
|
||||
}
|
||||
}
|
||||
|
||||
data class TokenWithBlockchain(val token: Token, val blockchain: Blockchain)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue