diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 8f2af51f2d..9d98629279 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -5,6 +5,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt index ad6edeb6d9..b2af3fbfaa 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/BaseScenarios.kt @@ -9,10 +9,11 @@ import com.tangem.tap.domain.sdk.mocks.MockContent import com.tangem.tap.domain.sdk.mocks.MockProvider import io.qameta.allure.kotlin.Allure.step -fun BaseTestCase.openMainScreen( +fun BaseTestCase.scanCard( productType: ProductType? = null, mockContent: MockContent? = null, - alreadyActivatedDialogIsShown : Boolean = false + alreadyActivatedDialogIsShown: Boolean = false, + isTwinsCard: Boolean = false, ) { if (productType != null) { MockProvider.setMocks(productType) @@ -32,6 +33,30 @@ fun BaseTestCase.openMainScreen( AlreadyUsedWalletDialogPageObject { thisIsMyWalletButton.click() } } } + if (isTwinsCard) { + step("Click on 'Continue' button") { + onOnboardingScreen { continueButton.clickWithAssertion() } + } + step("Click on 'Continue to my wallet' button") { + onOnboardingScreen { continueToMyWalletButton.clickWithAssertion() } + } + } +} + +fun BaseTestCase.openMainScreen( + productType: ProductType? = null, + mockContent: MockContent? = null, + alreadyActivatedDialogIsShown: Boolean = false, + isTwinsCard: Boolean = false, +) { + step("Scan card") { + scanCard( + productType = productType, + mockContent = mockContent, + alreadyActivatedDialogIsShown = alreadyActivatedDialogIsShown, + isTwinsCard = isTwinsCard, + ) + } step("Assert 'Main' screen is displayed") { onMainScreen { screenContainer.assertIsDisplayed() } } diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/CheckMainScreenScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/CheckMainScreenScenarios.kt new file mode 100644 index 0000000000..971702b746 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/CheckMainScreenScenarios.kt @@ -0,0 +1,116 @@ +package com.tangem.scenarios + +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.assertElementDoesNotExist +import com.tangem.screens.onMainScreen +import io.qameta.allure.kotlin.Allure.step + +fun BaseTestCase.checkSingleCurrencyMainScreen( + cardBlockchain: String, + cardTitle: String, + withTransactions: Boolean = false, + withWalletImage: Boolean = true +) { + step("Assert card title equal '$cardTitle'") { + onMainScreen { walletNameText.assertTextEquals(cardTitle) } + } + if (withTransactions) { + step("Assert 'Transactions' block is displayed") { + onMainScreen { transactionsExplorerText().assertExists() } + } + step("Assert 'Transactions' title is displayed") { + onMainScreen { transactionsTitle.assertIsDisplayed() } + } + step("Assert 'Explorer' icon is displayed") { + onMainScreen { transactionsExplorerIcon.assertIsDisplayed() } + } + } else { + step("Assert empty 'Transactions' block is displayed") { + onMainScreen { emptyTransactionBlock.assertIsDisplayed() } + } + step("Assert empty 'Transactions' block icon is displayed") { + onMainScreen { emptyTransactionBlockIcon.assertIsDisplayed() } + } + step("Assert empty 'Transactions' block text is displayed") { + onMainScreen { emptyTransactionBlockText.assertIsDisplayed() } + } + step("Assert empty 'Transactions' block 'Explore' button is displayed") { + onMainScreen { emptyTransactionBlockExploreButton.assertIsDisplayed() } + } + } + if (withWalletImage) { + step("Assert card image is displayed") { //TODO: create assertion method for checking images + onMainScreen { walletImage.assertIsDisplayed() } + } + } else { + step("Assert card image is not displayed") { + onMainScreen { walletImage.assertIsNotDisplayed() } + } + } + step("Assert 'Receive' button is displayed") { + onMainScreen { receiveButton.assertIsDisplayed() } + } + step("Assert 'Buy' button is displayed") { + onMainScreen { buyButton.assertIsDisplayed() } + } + step("Assert 'Send' button is displayed") { + onMainScreen { sendButton.assertIsDisplayed() } + } + step("Assert 'Sell' button is displayed") { + onMainScreen { sellButton.assertIsDisplayed() } + } + step("Assert 'Swap' button is not displayed") { + onMainScreen { swapButton.assertIsNotDisplayed() } + } + step("Assert 'Market Price' on single card main screen is displayed") { + onMainScreen { marketPriceBlock.assertIsDisplayed() } + } + step("Assert 'Market Price' title equals $cardBlockchain Market Price") { + onMainScreen { marketPriceText.assertTextContains("$cardBlockchain Market Price") } + } + step("Assert 'Organize tokens' button is not displayed") { + onMainScreen { + assertElementDoesNotExist({ organizeTokensButtonWithoutLazySearch() }, "Organize tokens button") + } + } +} + +fun BaseTestCase.checkMultiCurrencyMainScreen( + devicesCount: String, + cardTitle: String, + withWalletImage: Boolean = true +) { + step("Assert card title equal '$cardTitle'") { + onMainScreen { walletNameText.assertTextEquals(cardTitle) } + } + if (withWalletImage) { + step("Assert card image is displayed") { + onMainScreen { walletImage.assertIsDisplayed() } + } + } else { + step("Assert card image is not displayed") { + onMainScreen { walletImage.assertIsNotDisplayed() } + } + } + step("Assert devices count equal to '$devicesCount'") { + onMainScreen { walletDevicesCount.assertTextContains(devicesCount) } + } + step("Assert 'Buy' button is displayed") { + onMainScreen { buyButton.assertIsDisplayed() } + } + step("Assert 'Swap' button is displayed") { + onMainScreen { swapButton.assertIsDisplayed() } + } + step("Assert 'Sell' button is displayed") { + onMainScreen { sellButton.assertIsDisplayed() } + } + step("Assert 'Send' button is not displayed") { + onMainScreen { sendButton.assertIsNotDisplayed() } + } + step("Assert 'Receive' button is not displayed") { + onMainScreen { receiveButton.assertIsNotDisplayed() } + } + step("Assert 'Organize tokens' button is displayed") { + onMainScreen { organizeTokensButton().assertIsDisplayed() } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/OnboardingScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/OnboardingScenarios.kt new file mode 100644 index 0000000000..f13cbf8b67 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/OnboardingScenarios.kt @@ -0,0 +1,112 @@ +package com.tangem.scenarios + +import com.tangem.common.BaseTestCase +import com.tangem.screens.onOnboardingScreen +import io.qameta.allure.kotlin.Allure.step + +fun BaseTestCase.checkBackupScreen() { + step("Assert 'Creating a backup' top bar title is displayed") { + onOnboardingScreen { creatingBackupTobBarTitle.assertIsDisplayed() } + } + step("Assert 'Prepare your scan or ring' title is displayed") { + onOnboardingScreen { prepareYouCardOrRingTitle.assertIsDisplayed() } + } + step("Assert 'Start backup' text is displayed") { + onOnboardingScreen { startBackupText.assertIsDisplayed() } + } + step("Assert 'Scan card' button is displayed") { + onOnboardingScreen { scanCardButton.assertIsDisplayed() } + } + step("Assert 'Skip for later' button is not displayed") { + onOnboardingScreen { skipForLaterButton.assertIsNotDisplayed() } + } +} + +fun BaseTestCase.checkCreateWalletScreenForWalletNoWallets() { + step("Assert 'Create wallet' tob bar title is displayed") { + onOnboardingScreen { createWalletTopBarTitle.assertIsDisplayed() } + } + step("Assert top bar 'Back' button is displayed") { + onOnboardingScreen { topBarBackButton.assertIsDisplayed() } + } + step("Assert top bar 'More' button is displayed") { + onOnboardingScreen { topBarMoreButton.assertIsDisplayed() } + } + step("Assert 'Create wallet' title is displayed") { + onOnboardingScreen { createWalletTitle.assertIsDisplayed() } + } + step("Assert 'Create wallet' text is displayed") { + onOnboardingScreen { createWalletText.assertIsDisplayed() } + } + step("Assert 'Create wallet' button is displayed") { + onOnboardingScreen { createWalletButton.assertIsDisplayed() } + } +} + +fun BaseTestCase.openAndCheckBackupScreenForWalletNoWallets() { + step("Click on 'Create wallet' button") { + onOnboardingScreen { createWalletButton.performClick() } + } + step("Assert 'Getting started' top bar title is displayed") { + onOnboardingScreen { gettingStartedTobBarTitle.assertIsDisplayed() } + } + step("Assert 'Backup wallet' title is displayed") { + onOnboardingScreen { backupWalletTitle.assertIsDisplayed() } + } + step("Assert 'Backup wallet' text is displayed") { + onOnboardingScreen { backupWalletText.assertIsDisplayed() } + } + step("Assert 'Backup now' button is displayed") { + onOnboardingScreen { backupWalletButton.assertIsDisplayed() } + } + step("Assert 'Skip for later' button is not displayed") { + onOnboardingScreen { skipForLaterButton.assertIsNotDisplayed() } + } +} + +fun BaseTestCase.checkCreateWalletScreenForWallet2NoWallets() { + step("Assert 'Create a wallet' top bar title is displayed") { + onOnboardingScreen { createWalletTopBarTitle.assertIsDisplayed() } + } + step("Assert top bar 'Back' button is displayed") { + onOnboardingScreen { topBarBackButton.assertIsDisplayed() } + } + step("Assert top bar 'More' button is displayed") { + onOnboardingScreen { topBarMoreButton.assertIsDisplayed() } + } + step("Assert 'Generate keys privately' title is displayed") { + onOnboardingScreen { generateKeysPrivatelyTitle.assertIsDisplayed() } + } + step("Assert 'Generate keys privately' text is displayed") { + onOnboardingScreen { generateKeysPrivatelyText.assertIsDisplayed() } + } + step("Assert 'Create wallet' button is displayed") { + onOnboardingScreen { createWalletButton.assertIsDisplayed() } + } + step("Assert 'Other options' button is displayed") { + onOnboardingScreen { otherOptionsButton.assertIsDisplayed() } + } +} +fun BaseTestCase.openAndCheckBackupScreenForWallet2NoWallets() { + step("Click on 'Create wallet' button") { + onOnboardingScreen { createWalletButton.performClick() } + } + step("Assert 'Creating a backup' top bar title is displayed") { + onOnboardingScreen { creatingBackupTobBarTitle.assertIsDisplayed() } + } + step("Assert 'No backup devices' title is displayed") { + onOnboardingScreen { noBackupDevicesTitle.assertIsDisplayed() } + } + step("Assert 'No backup devices' text is displayed") { + onOnboardingScreen { noBackupDevicesText.assertIsDisplayed() } + } + step("Assert 'Add card or ring' button is displayed") { + onOnboardingScreen { addCardOrRingButton.assertIsDisplayed() } + } + step("Assert 'Finalize backup' button is displayed") { + onOnboardingScreen { finalizeBackupButton.assertIsDisplayed() } + } + step("Assert 'Skip for later' button is not displayed") { + onOnboardingScreen { skipForLaterButton.assertIsNotDisplayed() } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt index f8abf2f869..23e264e6f5 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt @@ -44,10 +44,30 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) } val buyButton: KNode = child { - hasTestTag(MainScreenTestTags.MULTI_CURRENCY_ACTION_BUTTON) + hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasText(getResourceString(R.string.common_buy)) } + val sendButton: KNode = child { + hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) + hasText(getResourceString(R.string.common_send)) + } + + val receiveButton: KNode = child { + hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) + hasText(getResourceString(R.string.common_receive)) + } + + val sellButton: KNode = child { + hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) + hasText(getResourceString(R.string.common_sell)) + } + + val swapButton: KNode = child { + hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) + hasText(getResourceString(R.string.common_swap)) + } + val walletNameText: KNode = child { hasTestTag(MainScreenTestTags.CARD_TITLE) useUnmergedTree = true @@ -58,6 +78,11 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) useUnmergedTree = true } + val walletDevicesCount: KNode = child { + hasTestTag(MainScreenTestTags.DEVICES_COUNT) + useUnmergedTree = true + } + val marketPriceBlock: KNode = child { hasTestTag(MarketPriceBlockTestTags.BLOCK) useUnmergedTree = true @@ -79,7 +104,7 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) useUnmergedTree = true } - fun transactionsExplorer(): KNode { + fun transactionsExplorerText(): KNode { return child { hasTestTag(TransactionHistoryBlockTestTags.EXPLORER_TEXT) hasText(getResourceString(R.string.common_explorer)) @@ -87,6 +112,22 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) } } + val emptyTransactionBlock: KNode = child { + hasTestTag(EmptyTransactionBlockTestTags.BLOCK) + } + + val emptyTransactionBlockIcon: KNode = child { + hasTestTag(EmptyTransactionBlockTestTags.ICON) + } + + val emptyTransactionBlockText: KNode = child { + hasTestTag(EmptyTransactionBlockTestTags.TEXT) + } + + val emptyTransactionBlockExploreButton: KNode = child { + hasTestTag(EmptyTransactionBlockTestTags.EXPLORE_BUTTON) + } + val notificationContainer: KNode = child { hasTestTag(NotificationTestTags.CONTAINER) useUnmergedTree = true diff --git a/app/src/androidTest/kotlin/com/tangem/screens/OnboardingPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/OnboardingPageObject.kt new file mode 100644 index 0000000000..ab0ed99290 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/OnboardingPageObject.kt @@ -0,0 +1,163 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.BaseButtonTestTags +import com.tangem.core.ui.test.StoriesScreenTestTags +import com.tangem.core.ui.test.TopAppBarTestTags +import com.tangem.wallet.R +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 io.github.kakaocup.kakao.common.views.KView +import io.github.kakaocup.kakao.text.KButton +import com.tangem.features.onboarding.v2.impl.R as OnboardingImplR + +class OnboardingPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val topBarBackButton: KNode = child { + hasTestTag(TopAppBarTestTags.CLOSE_BUTTON) + useUnmergedTree = true + } + + val continueButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(R.string.common_continue)) + useUnmergedTree = true + } + + val continueToMyWalletButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(R.string.onboarding_button_continue_wallet)) + useUnmergedTree = true + } + + val createWalletTopBarTitle: KNode = child { + hasTestTag(TopAppBarTestTags.TITLE) + hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_header)) + useUnmergedTree = true + } + + val createWalletTitle: KNode = child { + hasTestTag(StoriesScreenTestTags.TITLE) + hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_header)) + useUnmergedTree = true + } + + val createWalletText: KNode = child { + hasTestTag(StoriesScreenTestTags.TEXT) + hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_body)) + useUnmergedTree = true + } + + val gettingStartedTobBarTitle: KNode = child { + hasTestTag(TopAppBarTestTags.TITLE) + hasText(getResourceString(OnboardingImplR.string.onboarding_getting_started)) + useUnmergedTree = true + } + + val creatingBackupTobBarTitle: KNode = child { + hasTestTag(TopAppBarTestTags.TITLE) + hasText(getResourceString(OnboardingImplR.string.onboarding_navbar_title_creating_backup)) + useUnmergedTree = true + } + + val prepareYouCardOrRingTitle: KNode = child { + hasText(getResourceString(OnboardingImplR.string.onboarding_title_scan_origin_card)) + useUnmergedTree = true + } + + val startBackupText: KNode = child { + hasText(getResourceString(OnboardingImplR.string.onboarding_subtitle_scan_primary)) + useUnmergedTree = true + } + + val topBarMoreButton: KNode = child { + hasTestTag(TopAppBarTestTags.MORE_BUTTON) + } + + val backupWalletTitle: KNode = child { + hasText(getResourceString(OnboardingImplR.string.onboarding_wallet_info_title_first)) + useUnmergedTree = true + } + + val backupWalletText: KNode = child { + hasText(getResourceString(OnboardingImplR.string.onboarding_wallet_info_subtitle_first)) + useUnmergedTree = true + } + + val generateKeysPrivatelyTitle: KNode = child { + hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_options_title)) + useUnmergedTree = true + } + + val generateKeysPrivatelyText: KNode = child { + hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_options_message)) + useUnmergedTree = true + } + + val noBackupDevicesTitle: KNode = child { + hasText(getResourceString(OnboardingImplR.string.onboarding_subtitle_no_backup_cards)) + useUnmergedTree = true + } + + val noBackupDevicesText: KNode = child { + hasText(getResourceString(OnboardingImplR.string.onboarding_title_no_backup_cards)) + useUnmergedTree = true + } + + val createWalletButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_button_create_wallet)) + useUnmergedTree = true + } + + val backupWalletButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(OnboardingImplR.string.onboarding_button_backup_now)) + useUnmergedTree = true + } + + val scanCardButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(OnboardingImplR.string.onboarding_button_backup_card)) + useUnmergedTree = true + } + + val otherOptionsButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(OnboardingImplR.string.onboarding_create_wallet_options_button_options)) + useUnmergedTree = true + } + + val addCardOrRingButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(OnboardingImplR.string.onboarding_button_add_backup_card)) + useUnmergedTree = true + } + + val finalizeBackupButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(OnboardingImplR.string.onboarding_button_finalize_backup)) + useUnmergedTree = true + } + + val skipForLaterButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(OnboardingImplR.string.onboarding_button_skip_backup)) + useUnmergedTree = true + } + + val enableNFCAlert: KView = KView { + withId(R.id.alertTitle) + } + + val cancelButton: KButton = KButton { + withId(android.R.id.button2) + } +} + +internal fun BaseTestCase.onOnboardingScreen(function: OnboardingPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/StoriesPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/StoriesPageObject.kt index 63934bb015..f9666ceeb4 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/StoriesPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/StoriesPageObject.kt @@ -3,18 +3,12 @@ package com.tangem.screens import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import com.tangem.common.BaseTestCase import com.tangem.core.ui.test.StoriesScreenTestTags -import com.tangem.wallet.R 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.views.KView -import io.github.kakaocup.kakao.text.KButton class StoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : - ComposeScreen( - semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(StoriesScreenTestTags.SCREEN_CONTAINER) } - ) { + ComposeScreen(semanticsProvider = semanticsProvider) { val scanButton: KNode = child { hasTestTag(StoriesScreenTestTags.SCAN_BUTTON) @@ -23,14 +17,6 @@ class StoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : val orderButton: KNode = child { hasTestTag(StoriesScreenTestTags.ORDER_BUTTON) } - - val enableNFCAlert: KView = KView { - withId(R.id.alertTitle) - } - - val cancelButton: KButton = KButton { - withId(android.R.id.button2) - } } internal fun BaseTestCase.onStoriesScreen(function: StoriesPageObject.() -> Unit) = diff --git a/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsPageObject.kt index 3a0cd0962d..744837a668 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsPageObject.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import com.tangem.common.BaseTestCase import com.tangem.common.utils.LazyListItemNode +import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags import com.tangem.core.ui.test.BaseButtonTestTags import com.tangem.core.ui.test.NotificationTestTags import com.tangem.core.ui.test.TokenDetailsScreenTestTags @@ -85,7 +86,7 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide private val horizontalActionChips = KLazyListNode( semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(TokenDetailsScreenTestTags.HORIZONTAL_ACTION_CHIPS) }, + viewBuilderAction = { hasTestTag(BaseActionButtonsBlockTestTags.HORIZONTAL_ACTION_CHIPS) }, itemTypeBuilder = { itemType(::LazyListItemNode) }, positionMatcher = { position -> SemanticsMatcher.expectValue( @@ -97,25 +98,25 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide @OptIn(ExperimentalTestApi::class) val swapButton: LazyListItemNode = horizontalActionChips.childWith { - hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON) + hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasText(getResourceString(R.string.common_swap)) } @OptIn(ExperimentalTestApi::class) val sellButton: LazyListItemNode = horizontalActionChips.childWith { - hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON) + hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasText(getResourceString(R.string.common_sell)) } @OptIn(ExperimentalTestApi::class) val buyButton: LazyListItemNode = horizontalActionChips.childWith { - hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON) + hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasText(getResourceString(R.string.common_buy)) } @OptIn(ExperimentalTestApi::class) val sendButton: LazyListItemNode = horizontalActionChips.childWith { - hasTestTag(TokenDetailsScreenTestTags.ACTION_BUTTON) + hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON) hasText(getResourceString(R.string.common_send)) } diff --git a/app/src/androidTest/kotlin/com/tangem/steps/SingleCurrencyCardScenario.kt b/app/src/androidTest/kotlin/com/tangem/steps/SingleCurrencyCardScenario.kt deleted file mode 100644 index f9c68f5096..0000000000 --- a/app/src/androidTest/kotlin/com/tangem/steps/SingleCurrencyCardScenario.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.steps - -import com.tangem.common.BaseTestCase -import com.tangem.common.extensions.assertElementDoesNotExist -import com.tangem.screens.onMainScreen -import io.qameta.allure.kotlin.Allure.step - -fun BaseTestCase.checkSingleCurrencyMainScreen(cardBlockchain: String, cardTitle: String) { - step("Assert card title equal '$cardTitle'") { - onMainScreen { walletNameText.assertTextEquals(cardTitle) } - } - step("Assert 'Transactions' block is displayed") { - onMainScreen { transactionsExplorer().assertExists() } - } - step("Assert 'Transactions' title is displayer") { - onMainScreen { transactionsTitle.assertIsDisplayed() } - } - step("Assert 'Explorer Icon' is displayed") { - onMainScreen { transactionsExplorerIcon.assertIsDisplayed() } - } - step("Assert card image is displayed") { //TODO: Придумать нормальную проверку / реализовать скриншот-тестинг - onMainScreen { walletImage.assertIsDisplayed() } - } - step("Assert 'Market Price' on single card main screen is displayed") { - onMainScreen { marketPriceBlock.assertIsDisplayed() } - } - step("Assert 'Market Price' title equals $cardBlockchain Market Price") { - onMainScreen { marketPriceText.assertTextContains("$cardBlockchain Market Price") } - } - step("Assert 'Organize tokens' button is not displayed") { - onMainScreen { - assertElementDoesNotExist({ organizeTokensButtonWithoutLazySearch() }, "Organize tokens button") - } - } -} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/OnboardingTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/OnboardingTest.kt new file mode 100644 index 0000000000..c1e7d608de --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/OnboardingTest.kt @@ -0,0 +1,64 @@ +package com.tangem.tests + +import com.tangem.common.BaseTestCase +import com.tangem.scenarios.* +import com.tangem.tap.domain.sdk.mocks.content.ShibaNoBackupMockContent +import com.tangem.tap.domain.sdk.mocks.content.ShibaNoBackupNoWalletsMockContent +import com.tangem.tap.domain.sdk.mocks.content.Wallet2NoBackupMockContent +import com.tangem.tap.domain.sdk.mocks.content.Wallet2NoBackupNoWalletsMockContent +import dagger.hilt.android.testing.HiltAndroidTest +import io.qameta.allure.kotlin.AllureId +import io.qameta.allure.kotlin.junit4.DisplayName +import org.junit.Test + +@HiltAndroidTest +class OnboardingTest : BaseTestCase() { + + @AllureId("248") + @DisplayName("Onboarding: 'Shiba' no wallets backup screen test") + @Test + fun shibaNoWalletsBackupScreenTest() { + setupHooks().run { + scanCard(mockContent = ShibaNoBackupNoWalletsMockContent) + checkCreateWalletScreenForWalletNoWallets() + openAndCheckBackupScreenForWalletNoWallets() + } + } + + @AllureId("3989") + @DisplayName("Onboarding: 'Shiba' with wallets backup screen test") + @Test + fun shibaBackupScreenTest() { + setupHooks().run { + scanCard( + mockContent = ShibaNoBackupMockContent, + alreadyActivatedDialogIsShown = true + ) + checkBackupScreen() + } + } + + @AllureId("246") + @DisplayName("Onboarding: 'Wallet 2' no wallets backup screen test") + @Test + fun wallet2NoWalletsBackupScreenTest() { + setupHooks().run { + scanCard(mockContent = Wallet2NoBackupNoWalletsMockContent) + checkCreateWalletScreenForWallet2NoWallets() + openAndCheckBackupScreenForWallet2NoWallets() + } + } + + @AllureId("3990") + @DisplayName("Onboarding: 'Wallet 2' with wallets backup screen test") + @Test + fun wallet2BackupScreenTest() { + setupHooks().run { + scanCard( + mockContent = Wallet2NoBackupMockContent, + alreadyActivatedDialogIsShown = true + ) + checkBackupScreen() + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/ScanCardTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/ScanCardTest.kt new file mode 100644 index 0000000000..f1639df077 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/ScanCardTest.kt @@ -0,0 +1,196 @@ +package com.tangem.tests + +import com.tangem.common.BaseTestCase +import com.tangem.domain.models.scan.ProductType +import com.tangem.scenarios.checkMultiCurrencyMainScreen +import com.tangem.scenarios.checkSingleCurrencyMainScreen +import com.tangem.scenarios.openMainScreen +import com.tangem.tap.domain.sdk.mocks.MockContent +import com.tangem.tap.domain.sdk.mocks.content.* +import dagger.hilt.android.testing.HiltAndroidTest +import io.qameta.allure.kotlin.AllureId +import io.qameta.allure.kotlin.junit4.DisplayName +import org.junit.Test + +@HiltAndroidTest +class ScanCardTest : BaseTestCase() { + + @AllureId("868") + @DisplayName("Scan: Scanning single-currency cards") + @Test + fun singleTokenNoteCardScanTest() { + val cardBlockchain = "DOGE" + val cardType: ProductType = ProductType.Note + + setupHooks().run { + step("Open 'Main Screen' on '${cardType.name}' card") { + openMainScreen(cardType) + } + step("Check 'Main' screen for '${cardType.name}' $cardBlockchain card") { + checkSingleCurrencyMainScreen( + cardBlockchain = cardBlockchain, + cardTitle = cardType.name, + withTransactions = true + ) + } + } + } + + @AllureId("869") + @DisplayName("Scan: 'Twin' card") + @Test + fun twinsCardScanTest() { + val cardBlockchain = "BTC" + val cardType: MockContent = TwinsMockContent + val cardName = "Twin" + + setupHooks().run { + step("Open 'Main Screen' on '$cardName' card") { + openMainScreen(mockContent = cardType, isTwinsCard = true) + } + step("Check 'Main' screen for '$cardName' $cardBlockchain card") { + checkSingleCurrencyMainScreen(cardBlockchain = cardBlockchain, cardTitle = cardName) + } + } + } + + @AllureId("872") + @DisplayName("Scan: Card with Secp256k1 curve") + @Test + fun secpk1CurveCardScanTest() { + val devicesCount = "1 device" + val cardType: MockContent = Secpk1CurveMockContent + val cardName = "Wallet" + val card = "card with Secp256k1 curve" + + setupHooks().run { + step("Open 'Main Screen' on $card") { + openMainScreen(mockContent = cardType, alreadyActivatedDialogIsShown = true) + } + step("Check 'Main' screen for $card curve with devices count = '$devicesCount'") { + checkMultiCurrencyMainScreen( + devicesCount = devicesCount, + cardTitle = cardName, + withWalletImage = false + ) + } + } + } + + @AllureId("870") + @DisplayName("Scan: Card with Ed25519 curve") + @Test + fun edCurveCardScanTest() { + val cardBlockchain = "XLM" + val cardType: MockContent = EdCurveMockContent + val cardName = "Tangem card" + val card = "card with Ed25519 curve" + + setupHooks().run { + step("Open 'Main Screen' on $card") { + openMainScreen(mockContent = cardType) + } + step("Check 'Main' screen for $card with blockchain: '$cardBlockchain'") { + checkSingleCurrencyMainScreen( + cardBlockchain = cardBlockchain, + cardTitle = cardName, + withWalletImage = false + ) + } + } + } + + @AllureId("866") + @DisplayName("Scan: 'Shiba' card") + @Test + fun shibaCardScanTest() { + val devicesCount = "2 devices" + val cardType: MockContent = ShibaMockContent + val cardName = "Wallet" + val card = "Shiba" + + setupHooks().run { + step("Open 'Main Screen' on '$card' card") { + openMainScreen(mockContent = cardType, alreadyActivatedDialogIsShown = true) + } + step("Check 'Main' screen for '$card' card with devices count = '$devicesCount'") { + checkMultiCurrencyMainScreen(devicesCount, cardName) + } + } + } + + @AllureId("864") + @DisplayName("Scan: 'Ring'") + @Test + fun ringScanTest() { + val devicesCount = "3 devices" + val cardType: ProductType = ProductType.Ring + val cardName = "Wallet" + val ring = "Ring" + + setupHooks().run { + step("Open 'Main Screen' on '$ring'") { + openMainScreen(productType = cardType, alreadyActivatedDialogIsShown = true) + } + step("Check 'Main' screen for '$ring' with devices count = '$devicesCount'") { + checkMultiCurrencyMainScreen(devicesCount, cardName) + } + } + } + + @AllureId("867") + @DisplayName("Scan: 'Wallet' card") + @Test + fun walletCardScanTest() { + val devicesCount = "1 device" + val cardType: ProductType = ProductType.Wallet + val cardName = "Wallet" + + setupHooks().run { + step("Open 'Main Screen' on '$cardName' card") { + openMainScreen(productType = cardType) + } + step("Check 'Main' screen for '$cardName' card with devices count = '$devicesCount'") { + checkMultiCurrencyMainScreen(devicesCount, cardName) + } + } + } + + @AllureId("865") + @DisplayName("Scan: 'Wallet 2' card") + @Test + fun wallet2ScanTest() { + val devicesCount = "2 devices" + val cardType: MockContent = Wallet2MockContent + val cardName = "Wallet" + val card = "Wallet 2" + + setupHooks().run { + step("Open 'Main Screen' on '$card' card") { + openMainScreen(mockContent = cardType, alreadyActivatedDialogIsShown = true) + } + step("Check 'Main' screen for '$card' card with devices count = '$devicesCount'") { + checkMultiCurrencyMainScreen(devicesCount, cardName) + } + } + } + + @AllureId("871") + @DisplayName("Scan: Card with 4.12 firmware") + @Test + fun firmware412CardScanTest() { + val devicesCount = "1 device" + val cardType: MockContent = Firmware412MockContent + val cardName = "Tangem card" + val card = "card with 4.12 firmware" + + setupHooks().run { + step("Open 'Main Screen' on '$card'") { + openMainScreen(mockContent = cardType) + } + step("Check 'Main' screen for '$card' with devices count = '$devicesCount'") { + checkMultiCurrencyMainScreen(devicesCount, cardName) + } + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/SingleCurrencyCardsScan.kt b/app/src/androidTest/kotlin/com/tangem/tests/SingleCurrencyCardsScan.kt deleted file mode 100644 index 34d745794f..0000000000 --- a/app/src/androidTest/kotlin/com/tangem/tests/SingleCurrencyCardsScan.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.tests - -import com.tangem.common.BaseTestCase -import com.tangem.domain.models.scan.ProductType -import com.tangem.scenarios.openMainScreen -import com.tangem.steps.checkSingleCurrencyMainScreen -import dagger.hilt.android.testing.HiltAndroidTest -import io.qameta.allure.kotlin.AllureId -import io.qameta.allure.kotlin.junit4.DisplayName -import org.junit.Test - -@HiltAndroidTest -class SingleCurrencyCardsScan : BaseTestCase() { - - @AllureId("868") - @DisplayName("Scan: Scanning single-currency cards") - @Test - fun singleTokenNoteScanTest() { - val cardBlockchain = "DOGE" - val cardType: ProductType = ProductType.Note - - setupHooks().run { - step("Open 'Main Screen' on ${cardType.name} card") { - openMainScreen(cardType) - } - step("Check 'Main' screen for ${cardType.name} $cardBlockchain card") { - checkSingleCurrencyMainScreen(cardBlockchain = cardBlockchain, cardTitle = cardType.name) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ee754f78cc..882377e5e5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,6 +33,7 @@ analyticsEventsHandler.send( - WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(it.second), + WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(hasImmortalTransaction), ) } } diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 9a3f617366..164150ae73 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -88,7 +88,7 @@ lateinit var store: Store lateinit var foregroundActivityObserver: ForegroundActivityObserver internal lateinit var derivationsFinder: DerivationsFinder -abstract class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider { +open class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider { // region DI private val entryPoint: ApplicationEntryPoint diff --git a/app/src/main/java/com/tangem/tap/common/TestActions.kt b/app/src/main/java/com/tangem/tap/common/TestActions.kt index 5dc923cf58..95edcd851e 100644 --- a/app/src/main/java/com/tangem/tap/common/TestActions.kt +++ b/app/src/main/java/com/tangem/tap/common/TestActions.kt @@ -7,7 +7,7 @@ package com.tangem.tap.common object TestActions { // It used only for the test actions in debug or debug_beta builds - var testAmountInjectionForWalletManagerEnabled = false + var isTestAmountInjectionForWalletManagerEnabled = false } typealias TestAction = Pair Unit> \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt index 4f32eee854..a94aa24beb 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt @@ -58,7 +58,7 @@ private class BlockchainSdkErrorConverter( if (value.customMessage.contains(DemoTransactionSender.ID)) return emptyMap() if (value is BlockchainSdkError.WrappedTangemError) { - return (value.tangemError as? TangemSdkError)?.let { cardSdkErrorConverter.convert(it) } ?: emptyMap() + return (value.tangemError as? TangemSdkError)?.let { cardSdkErrorConverter.convert(it) }.orEmpty() } return mapOf( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt index f4b9dbc61f..e239d1ccc6 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt @@ -7,7 +7,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent */ sealed class Chat( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : AnalyticsEvent("Chat", event, params) { class ScreenOpened : Chat("Chat Screen Opened") diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt index f35a636601..a4fcb32fe0 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt @@ -8,7 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent sealed class Onboarding( category: String, event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : AnalyticsEvent(category, event, params) { class Started : Onboarding("Onboarding", "Onboarding Started") @@ -16,7 +16,7 @@ sealed class Onboarding( sealed class CreateWallet( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : Onboarding("Onboarding / Create Wallet", event, params) { class ScreenOpened : CreateWallet("Create Wallet Screen Opened") @@ -36,7 +36,7 @@ sealed class Onboarding( sealed class Backup( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : Onboarding("Onboarding / Backup", event, params) { class ScreenOpened : Backup("Backup Screen Opened") @@ -64,7 +64,7 @@ sealed class Onboarding( sealed class Topup( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : Onboarding("Onboarding / Top Up", event, params) { class ScreenOpened : Topup("Activation Screen Opened") @@ -79,7 +79,7 @@ sealed class Onboarding( sealed class Twins( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : Onboarding("Onboarding / Twins", event, params) { class ScreenOpened : Twins("Twinning Screen Opened") diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index 4234256e8c..3ac65172d2 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -8,7 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent sealed class Settings( category: String = "Settings", event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : AnalyticsEvent(category, event, params) { class ScreenOpened : Settings(event = "Settings Screen Opened") @@ -17,7 +17,7 @@ sealed class Settings( sealed class CardSettings( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : Settings("Settings / Card Settings", event, params) { class ButtonFactoryReset : CardSettings("Button - Factory Reset") @@ -54,7 +54,7 @@ sealed class Settings( sealed class AppSettings( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : Settings(category = "Settings / App Settings", event = event, params = params) { class SaveWalletSwitcherChanged(state: AnalyticsParam.OnOffState) : AppSettings( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt index a9a52c5e25..d34334ec34 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt @@ -7,7 +7,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent */ sealed class SignIn( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : AnalyticsEvent("Sign In", event, params) { class ScreenOpened : SignIn(event = "Sign In Screen Opened") diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index 9f6214a78a..bac4bcc6d2 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -9,12 +9,12 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM sealed class Token( category: String, event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : AnalyticsEvent(category, event, params) { sealed class Receive( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : Token("Token / Receive", event, params) { class ScreenOpened( @@ -29,7 +29,7 @@ sealed class Token( sealed class Topup( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : Token("Token / Topup", event, params) { class ScreenOpened : Topup("Top Up Screen Opened") @@ -38,7 +38,7 @@ sealed class Token( sealed class Withdraw( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : Token("Token / Withdraw", event, params) { class ScreenOpened : Withdraw("Withdraw Screen Opened") diff --git a/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt b/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt index 73f2ebd5ef..1c33fe0909 100644 --- a/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt +++ b/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt @@ -19,6 +19,7 @@ internal class DefaultClipboardManager(private val clipboardManager: AndroidClip clipboardManager.setPrimaryClip(clip) } + @Suppress("UseIsNullOrEmpty") override fun getText(default: String?): String? { val clip = clipboardManager.primaryClip diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 5d48278690..62bf0eb950 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -90,6 +90,6 @@ fun Store.dispatchNavigationAction(action: AppRouter.() -> Unit) { inline fun Store.inject(getDependency: DaggerGraphState.() -> T?): T { return requireNotNull(state.daggerGraphState.getDependency()) { - "${T::class.simpleName} isn't initialized " + "${T::class.simpleName.orEmpty()} isn't initialized " } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index 55ebaa1ea3..3aeeb7d9cd 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -22,9 +22,9 @@ import timber.log.Timber ) @Suppress("MagicNumber") suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result = try { - if (isDemoCard || TestActions.testAmountInjectionForWalletManagerEnabled) { + if (isDemoCard || TestActions.isTestAmountInjectionForWalletManagerEnabled) { delay(500) - TestActions.testAmountInjectionForWalletManagerEnabled = false + TestActions.isTestAmountInjectionForWalletManagerEnabled = false Result.Success(wallet) } else { update() @@ -35,7 +35,7 @@ suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result = try val networkConnectionManager = store.inject(DaggerGraphState::networkConnectionManager) if (!networkConnectionManager.isOnline) { - Result.Failure(TapError.NoInternetConnection) + Result.Failure(TapError.NoInternetConnection()) } else { val blockchain = wallet.blockchain val amountToCreateAccount = blockchain.amountToCreateAccount(this, wallet.getFirstToken()) diff --git a/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt b/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt index a2011ff5e7..924cbede00 100644 --- a/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt +++ b/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt @@ -44,6 +44,7 @@ class TangemAppLoggerInitializer( } } + @Suppress("BooleanPropertyNaming") private companion object { val IS_LOG_ENABLED: Boolean = BuildConfig.LOG_ENABLED val PERMITTED_PRIORITY = listOf(Log.ERROR, Log.INFO) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 090cbc72f8..598ad3be24 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -20,6 +20,6 @@ data class GlobalState( typealias CryptoCurrencyName = String data class OnboardingState( - val onboardingStarted: Boolean = false, + val isOnboardingStarted: Boolean = false, val shouldResetOnCreate: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt b/app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt index 8d7beb3948..12e69c91b8 100644 --- a/app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt +++ b/app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt @@ -61,9 +61,9 @@ internal class AndroidEmailSender : EmailSender { .setSubject(email.subject) .setText(email.message) - email.attachment?.let { + email.attachment?.let { file -> builder.setStream( - FileProvider.getUriForFile(activity, "${activity.packageName}.provider", it), + FileProvider.getUriForFile(activity, "${activity.packageName}.provider", file), ) } diff --git a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt index f990fdc0fd..19171195db 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt @@ -14,7 +14,6 @@ import javax.inject.Singleton import kotlin.text.encodeToByteArray private const val DEFAULT_KEY = "tangem_pay_default_key" -private const val DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY = "tangem_pay_default_customer_wallet_address_key" @Singleton internal class DefaultTangemPayStorage @Inject constructor( @@ -54,21 +53,6 @@ internal class DefaultTangemPayStorage @Inject constructor( ?.let(tokensAdapter::fromJson) } - /** - * Store the only customer wallet address, since for the f&f user can issue only one card tied to one address - */ - override suspend fun storeCustomerWalletAddress(customerWalletAddress: String) = - withContext(dispatcherProvider.io) { - secureStorage.store( - customerWalletAddress.encodeToByteArray(throwOnInvalidSequence = true), - DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY, - ) - } - - override suspend fun getCustomerWalletAddress(): String? = withContext(dispatcherProvider.io) { - secureStorage.get(DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY)?.decodeToString(throwOnInvalidSequence = true) - } - override suspend fun clear(customerWalletAddress: String) = withContext(dispatcherProvider.io) { secureStorage.delete(createKey(customerWalletAddress)) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 726ee06efc..960857e2eb 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -24,6 +24,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository +import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository import com.tangem.domain.tokens.wallet.WalletBalanceFetcher import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles @@ -455,4 +456,21 @@ internal object TokensDomainModule { ): SaveViewedTokenReceiveWarningUseCase { return SaveViewedTokenReceiveWarningUseCase(tokenReceiveWarningsViewedRepository) } + + @Provides + @Singleton + fun provideNeedShowYieldSupplyDepositedWarningUseCase( + yieldSupplyWarningsViewedRepository: YieldSupplyWarningsViewedRepository, + dispatchers: CoroutineDispatcherProvider, + ): NeedShowYieldSupplyDepositedWarningUseCase { + return NeedShowYieldSupplyDepositedWarningUseCase(yieldSupplyWarningsViewedRepository, dispatchers) + } + + @Provides + @Singleton + fun provideSaveViewedYieldSupplyWarningUseCase( + yieldSupplyWarningsViewedRepository: YieldSupplyWarningsViewedRepository, + ): SaveViewedYieldSupplyWarningUseCase { + return SaveViewedYieldSupplyWarningUseCase(yieldSupplyWarningsViewedRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt index 1b342eb608..a047e3deae 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt @@ -4,6 +4,7 @@ import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.error.FeeErrorResolver import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetContractAddressUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase import dagger.Module @@ -47,4 +48,15 @@ internal object YieldSupplyDomainModule { feeErrorResolver = feeErrorResolver, ) } + + @Provides + @Singleton + fun provideYieldSupplyGetContractAddressUseCase( + yieldSupplyTransactionRepository: YieldSupplyTransactionRepository, + ): YieldSupplyGetContractAddressUseCase { + return YieldSupplyGetContractAddressUseCase( + yieldSupplyTransactionRepository = yieldSupplyTransactionRepository, + + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt index dffd82ec11..67e1652a6e 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt @@ -39,7 +39,7 @@ class TangemSigner( TangemSignerResponse( totalSignedHashes = result.data.totalSignedHashes, remainingSignatures = result.data.remainingSignatures, - isRing = result.data.batchId?.let(::isRing) ?: false, + isRing = result.data.batchId?.let(::isRing) == true, ), ) if (continuation.isActive) { @@ -85,7 +85,7 @@ class TangemSigner( TangemSignerResponse( totalSignedHashes = result.data.totalSignedHashes, remainingSignatures = result.data.remainingSignatures, - isRing = result.data.batchId?.let(::isRing) ?: false, + isRing = result.data.batchId?.let(::isRing) == true, ), ) if (continuation.isActive) { diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index 1fc37a0267..2f1677fc5d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -20,23 +20,23 @@ sealed class TapError( override val args: List? = null, ) : Throwable(), TapErrors, ArgError { - object UnknownError : TapError(R.string.send_error_unknown) + class UnknownError : TapError(R.string.send_error_unknown) open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage)) - object NoInternetConnection : TapError(R.string.wallet_notification_no_internet) + class NoInternetConnection : TapError(R.string.wallet_notification_no_internet) sealed class WalletManager { class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount) class InternalError(message: String) : CustomError(message) - object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later) + class BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later) } } sealed class TapSdkError(override val messageResId: Int?) : TangemError(code = 50100) { override var customMessage: String = code.toString() - object CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card) - object CardNotSupportedByRelease : TapSdkError(R.string.error_wrong_card_type) + class CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card) + class CardNotSupportedByRelease : TapSdkError(R.string.error_wrong_card_type) } fun TapErrors.assembleErrors(): MutableList?>> { diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt index b86f57ce33..cd94802690 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt @@ -70,7 +70,7 @@ internal class DefaultResetCardUseCase( null } - type?.let { + if (type != null) { tangemSdkManager.setUserCodeRequestPolicy(policy = UserCodeRequestPolicy.Always(type)) } } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index 5b44a91292..17ce65a238 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -119,14 +119,14 @@ internal class LegacyScanProcessor @Inject constructor( } private fun sendAnalytics(analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse) { - analyticsEvent?.let { + analyticsEvent?.let { event -> // this workaround needed to send CardWasScannedEvent without adding a context val interceptor = CardContextInterceptor(scanResponse) - val params = it.params.toMutableMap() + val params = event.params.toMutableMap() interceptor.intercept(params) - it.params = params.toMap() + event.params = params.toMap() - Analytics.send(it) + Analytics.send(event) } } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt index abf3f1f23d..6076dd07d3 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -33,8 +33,8 @@ internal object UseCaseScanProcessor { return scanCardUseCase(cardId, allowsRequestAccessCodeFromRepository) .fold( - ifLeft = { - val error = scanCardExceptionConverter.convertBack(it) + ifLeft = { scanCardException -> + val error = scanCardExceptionConverter.convertBack(scanCardException) Analytics.sendErrorEvent(TangemSdkErrorEvent(error)) CompletionResult.Failure(error) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt index b36fb89f0e..cd7a62cab3 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt @@ -8,10 +8,10 @@ sealed class ScanChainException : ScanCardException.ChainException() { /** * May be returned from [DisclaimerChain] * */ - data object DisclaimerWasCanceled : ScanChainException() { + class DisclaimerWasCanceled : ScanChainException() { @Suppress("UnusedPrivateMember") - private fun readResolve(): Any = DisclaimerWasCanceled + private fun readResolve(): Any = DisclaimerWasCanceled() } /** diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt index 3019dbeba0..704c10dd50 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt @@ -5,27 +5,25 @@ import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.domain.models.scan.ProductType import com.tangem.sdk.api.CreateProductWalletTaskResponse -import com.tangem.tap.domain.sdk.mocks.content.Wallet2WithSeedPhraseMockContent -import com.tangem.tap.domain.sdk.mocks.content.NoteMockContent -import com.tangem.tap.domain.sdk.mocks.content.WalletMockContent +import com.tangem.tap.domain.sdk.mocks.content.* object MockProvider { private var content: MockContent = getMockContent(ProductType.Wallet) - private var emulateError: Boolean = false + private var isEmulatingError: Boolean = false private var emulatedError: TangemError = TangemSdkError.TagLost() fun setEmulateError(error: TangemError? = null) { - emulateError = true + isEmulatingError = true error?.let { emulatedError = it } } fun resetEmulateError() { - emulateError = false + isEmulatingError = false } fun setMocks(productType: ProductType) { @@ -69,12 +67,14 @@ object MockProvider { ProductType.Wallet -> WalletMockContent ProductType.Wallet2 -> Wallet2WithSeedPhraseMockContent ProductType.Note -> NoteMockContent + ProductType.Ring -> RingMockContent + ProductType.Twins -> TwinsMockContent else -> TODO() } } private fun CompletionResult.Success.orFailure(): CompletionResult { - return if (emulateError) { + return if (isEmulatingError) { CompletionResult.Failure(emulatedError) } else { this diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/BackupWalletMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/BackupWalletMockContent.kt index c2c8d9ba23..284c59ef1a 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/BackupWalletMockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/BackupWalletMockContent.kt @@ -168,52 +168,52 @@ object BackupWalletMockContent : MockContent { byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), ) to - ExtendedPublicKeysMap( - mapOf( - DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth - publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), - chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch - publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), - chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge - publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), - chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( - publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), - chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, ), ), + ), ), ) @@ -234,24 +234,24 @@ object BackupWalletMockContent : MockContent { byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), ) to - ExtendedPublicKeysMap( - mapOf( - DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth - publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), - chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, ), ), + ), ), primaryCard = primaryCard, ) diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/DevWalletMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/DevWalletMockContent.kt index 29b0ec5fee..e37fa805a0 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/DevWalletMockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/DevWalletMockContent.kt @@ -168,52 +168,52 @@ object DevWalletMockContent : MockContent { byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), ) to - ExtendedPublicKeysMap( - mapOf( - DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth - publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), - chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch - publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), - chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge - publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), - chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( - publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), - chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, ), ), + ), ), ) @@ -234,24 +234,24 @@ object DevWalletMockContent : MockContent { byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), ) to - ExtendedPublicKeysMap( - mapOf( - DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth - publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), - chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, ), ), + ), ), primaryCard = primaryCard, ) diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/EdCurveMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/EdCurveMockContent.kt new file mode 100644 index 0000000000..3629378cad --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/EdCurveMockContent.kt @@ -0,0 +1,116 @@ +package com.tangem.tap.domain.sdk.mocks.content + +import com.tangem.common.SuccessResponse +import com.tangem.common.card.* +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.tap.domain.sdk.mocks.MockContent +import java.util.Date + +object EdCurveMockContent : MockContent { + + override val cardDto = CardDTO( + cardId = "CB43000000000004", + batchId = "0052", + cardPublicKey = byteArrayOf(4, 20, -44, 84, -19, -94, 62, -88, 17, -60, -54, 115, 109, -105, 116, -4, 79, 74, 50, 55, -57, 118, -84, -17, -64, 74, 98, -104, -11, 101, 64, -85, -23, -69, 18, 96, -105, -125, -93, 87, -9, -96, -38, 32, -99, -116, 124, -34, -59, 64, -125, 96, 94, 47, -128, 61, 58, -100, 103, 84, 21, -103, 77, -91, 64), + firmwareVersion = CardDTO.FirmwareVersion( + major = 3, + minor = 5, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1583971200), + signature = byteArrayOf(54, 94, -56, 20, 66, -33, 103, 86, -108, 28, -101, -113, 31, 118, -89, 86, 75, 4, 21, -70, -115, 82, -98, 45, -95, 92, 45, 29, -33, 53, -16, 9, -27, 44, 44, 13, 9, -71, -85, -43, -3, 58, -82, -40, -59, -114, 7, 89, 39, -16, -60, -58, 95, -87, -54, -71, -70, -95, -59, -44, 125, -42, -66, -26), + ), + issuer = CardDTO.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 1, + isSettingAccessCodeAllowed = false, + isSettingPasscodeAllowed = true, + isResettingUserCodesAllowed = true, + isLinkedTerminalEnabled = true, + isBackupAllowed = false, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = true, + isHDWalletAllowed = false, + isKeysImportAllowed = false, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = false), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = false, + isPasscodeSet = null, + supportedCurves = listOf(EllipticCurve.Ed25519), + wallets = listOf( + CardDTO.Wallet( + publicKey = byteArrayOf(71, -83, -58, -126, 43, 71, -27, 47, 85, -5, 120, -20, -124, -68, 14, 64, 61, 47, -95, -125, 76, -56, -98, 12, 70, 2, -18, -73, 4, -29, 62, -124), + chainCode = null, + curve = EllipticCurve.Ed25519, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = 1000000, + index = 0, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = null, + isImported = false, + ), + ), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = null, + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Wallet, + walletData = WalletData( + blockchain = "XLM", + token = null, + ), + secondTwinPublicKey = null, + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = emptyMap(), + ) + + override val extendedPublicKey + get() = error("Available only for Wallet+") + + override val successResponse = SuccessResponse(cardId = "CB43000000000004") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = error("Available only for Wallet 2") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Firmware412MockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Firmware412MockContent.kt new file mode 100644 index 0000000000..ae5b7e8beb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Firmware412MockContent.kt @@ -0,0 +1,266 @@ +package com.tangem.tap.domain.sdk.mocks.content + +import com.tangem.common.SuccessResponse +import com.tangem.common.card.* +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.tap.domain.sdk.mocks.MockContent +import java.util.Date + +object Firmware412MockContent : MockContent { + + private val primaryCard = PrimaryCard( + cardId = "AC07000000035437", + batchId = "CB79", + cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39), + linkingKey = byteArrayOf( + 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, + -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, + ), + existingWalletsCount = 3, isHDWalletAllowed = true, + issuer = Card.Issuer( + name = "TANGEM AG", + publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2), + ), + manufacturer = Card.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1743759687), + signature = byteArrayOf(), + ), + walletCurves = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519, EllipticCurve.Bls12381G2Aug), + firmwareVersion = FirmwareVersion( + major = 4, + minor = 12, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + isKeysImportAllowed = false, + certificate = null, + ) + + override val cardDto = CardDTO( + cardId = "AC07000000035437", + batchId = "CB79", + cardPublicKey = byteArrayOf(2, -120, -3, -32, -122, -127, -120, -104, 59, 72, 76, 114, 94, 75, -37, -55, 55, 99, 66, 123, 85, -87, 80, 106, 105, -116, 87, -83, -12, 70, 108, -68, -39), + firmwareVersion = CardDTO.FirmwareVersion( + major = 4, + minor = 12, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1649635200000), + signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66), + ), + issuer = CardDTO.Issuer( + name = "TANGEM AG", + publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 20, + isSettingAccessCodeAllowed = false, + isSettingPasscodeAllowed = false, + isResettingUserCodesAllowed = true, + isLinkedTerminalEnabled = true, + isBackupAllowed = false, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = false, + isHDWalletAllowed = false, + isKeysImportAllowed = false, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = false), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = false, + isPasscodeSet = false, + supportedCurves = listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Secp256r1, + ), + wallets = listOf( + CardDTO.Wallet( + publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14), + curve = EllipticCurve.Secp256k1, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 0, + hasBackup = false, + derivedKeys = mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + ), + DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), + ), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14), + ), + isImported = false, + ), + CardDTO.Wallet( + publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109), + chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6), + curve = EllipticCurve.Ed25519, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 1, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109), + chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6), + ), + isImported = false, + ), + ), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = CardDTO.BackupStatus.NoBackup, + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Wallet, + walletData = null, + secondTwinPublicKey = null, + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = mapOf( + ByteArrayKey( + byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + ) + + override val extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110), + chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ) + + override val successResponse = SuccessResponse(cardId = "AC07000000035437") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = mapOf( + ByteArrayKey( + byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + primaryCard = primaryCard, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = error("Available only for Wallet 2") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/RingMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/RingMockContent.kt new file mode 100644 index 0000000000..de2571ece4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/RingMockContent.kt @@ -0,0 +1,270 @@ +package com.tangem.tap.domain.sdk.mocks.content + +import com.tangem.common.SuccessResponse +import com.tangem.common.card.* +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.tap.domain.sdk.mocks.MockContent +import java.util.Date + +object RingMockContent : MockContent { + + private val primaryCard = PrimaryCard( + cardId = "BA00000100129772", + batchId = "BA000001", + cardPublicKey = byteArrayOf(2, 48, 4, 54, -34, -33, 22, 53, -92, 8, 72, 50, 69, 1, 105, -53, 9, -126, -23, -61, -62, -85, -24, -112, -32, 76, -88, -66, -25, -83, -70, -95, 90), + linkingKey = byteArrayOf( + 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, + -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, + ), + existingWalletsCount = 3, isHDWalletAllowed = true, + issuer = Card.Issuer( + name = "Tangem 2.0", + publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34), + ), + manufacturer = Card.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1743759687), + signature = byteArrayOf(44, 8, 116, 111, -57, 20, -93, 101, -104, 97, -104, -15, 58, -8, 41, -53, 95, 99, 107, 47, -29, -118, 41, 64, 22, 94, 33, -81, 114, -47, 10, -16, 97, 14, 94, -36, -82, -108, 74, -9, 35, -38, 66, 67, -116, -55, 65, -30, -58, -33, 31, 120, -45, 42, -3, -120, -74, -97, 97, -102, -13, -28, 29, -41), + ), + walletCurves = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519, EllipticCurve.Bls12381G2Aug), + firmwareVersion = FirmwareVersion( + major = 6, + minor = 33, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + isKeysImportAllowed = false, + certificate = null, + ) + + override val cardDto = CardDTO( + cardId = "BA00000100129772", + batchId = "BA000001", + cardPublicKey = byteArrayOf(2, 48, 4, 54, -34, -33, 22, 53, -92, 8, 72, 50, 69, 1, 105, -53, 9, -126, -23, -61, -62, -85, -24, -112, -32, 76, -88, -66, -25, -83, -70, -95, 90), + firmwareVersion = CardDTO.FirmwareVersion( + major = 6, + minor = 33, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1649635200000), + signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66), + ), + issuer = CardDTO.Issuer( + name = "Tangem 2.0", + publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 20, + isSettingAccessCodeAllowed = true, + isSettingPasscodeAllowed = false, + isResettingUserCodesAllowed = true, + isLinkedTerminalEnabled = true, + isBackupAllowed = true, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = true, + isHDWalletAllowed = true, + isKeysImportAllowed = true, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = true, + isPasscodeSet = false, + supportedCurves = listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Secp256r1, + EllipticCurve.Bls12381G2, + EllipticCurve.Bls12381G2Aug, + EllipticCurve.Bls12381G2Pop, + EllipticCurve.Bip0340, + ), + wallets = listOf( + CardDTO.Wallet( + publicKey = byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + chainCode = byteArrayOf(74, -90, -85, -117, -116, -78, 98, 63, 83, 12, 43, -69, 43, -54, -119, -62, 80, 107, -127, 53, 73, 108, 114, -102, 81, -123, 68, 92, 65, -25, 107, 86), + curve = EllipticCurve.Secp256k1, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 0, + hasBackup = true, + derivedKeys = mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + ), + DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), + ), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14), + ), + isImported = false, + ), + CardDTO.Wallet( + publicKey = byteArrayOf(-123, -48, -77, -113, -63, -124, -18, 36, -14, -127, 104, 89, 48, 103, 69, -12, 107, -103, 64, -2, 97, 126, -78, -99, -9, -72, 94, -68, 30, 43, 38, 42), + chainCode = byteArrayOf(104, 109, 56, -52, 125, -119, -49, -51, -46, -122, -111, 75, 51, 103, 15, 25, -101, 72, -101, 101, -128, -2, -51, 3, 74, 62, -49, -6, -95, 91, -104, -58), + curve = EllipticCurve.Ed25519, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 1, + hasBackup = true, + derivedKeys = emptyMap(), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109), + chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6), + ), + isImported = false, + ), + ), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = CardDTO.BackupStatus.Active(cardCount = 2), + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Ring, + walletData = null, + secondTwinPublicKey = null, + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + ) + + override val extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110), + chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ) + + override val successResponse = SuccessResponse(cardId = "BA00000100129772") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + primaryCard = primaryCard, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = error("Available only for Wallet 2") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Secpk1CurveMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Secpk1CurveMockContent.kt new file mode 100644 index 0000000000..61310fcb8e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Secpk1CurveMockContent.kt @@ -0,0 +1,188 @@ +package com.tangem.tap.domain.sdk.mocks.content + +import com.tangem.common.SuccessResponse +import com.tangem.common.card.* +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.tap.domain.sdk.mocks.MockContent +import java.util.Date + +object Secpk1CurveMockContent : MockContent { + + private val primaryCard = PrimaryCard( + cardId = "CB37000000000002", + batchId = "0045", + cardPublicKey = byteArrayOf(4, 9, -112, -60, 29, -62, 102, -43, -38, -63, 56, 87, -76, 74, 91, -68, 127, -98, -56, 15, 12, -52, 37, -99, -104, -104, -7, -111, 6, 86, -90, 44, 87, 18, 73, 126, -104, 9, 83, -17, -11, -80, 115, 86, -90, -97, 119, -66, 114, 48, -95, -40, 10, 64, 121, -40, -15, 92, -64, 31, -21, 126, -31, 0, -40), + linkingKey = byteArrayOf( + 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, + -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, + ), + existingWalletsCount = 3, + isHDWalletAllowed = false, + issuer = Card.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(4, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34, -11, 83, -57, -34, -17, -75, 79, -45, -44, -13, 97, -47, 78, 109, -61, -15, 27, 125, 78, -95, -125, 37, 10, 96, 114, 14, -67, -7, -31, 16, -51, 38), + ), + manufacturer = Card.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1743759687), + signature = byteArrayOf(18, -101, -119, 75, 82, 4, 71, -45, 4, 74, -109, 68, 100, 48, 49, 82, 35, 13, 35, -43, 14, -123, -15, -13, -94, 122, -8, 12, -73, -52, 48, 16, 112, 41, 17, 52, -51, 38, 123, 50, 97, -32, -12, 76, -102, 29, -39, 126, 55, 65, -5, -1, -32, -28, -10, -33, 77, 105, -24, 93, -30, 73, -77, 82), + ), + walletCurves = listOf(EllipticCurve.Secp256k1), + firmwareVersion = FirmwareVersion( + major = 3, + minor = 5, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + isKeysImportAllowed = false, + certificate = null, + ) + + override val cardDto = CardDTO( + cardId = "CB37000000000002", + batchId = "0045", + cardPublicKey = byteArrayOf(4, 9, -112, -60, 29, -62, 102, -43, -38, -63, 56, 87, -76, 74, 91, -68, 127, -98, -56, 15, 12, -52, 37, -99, -104, -104, -7, -111, 6, 86, -90, 44, 87, 18, 73, 126, -104, 9, 83, -17, -11, -80, 115, 86, -90, -97, 119, -66, 114, 48, -95, -40, 10, 64, 121, -40, -15, 92, -64, 31, -21, 126, -31, 0, -40), + firmwareVersion = CardDTO.FirmwareVersion( + major = 3, + minor = 5, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1649635200000), + signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66), + ), + issuer = CardDTO.Issuer( + name = "TANGEM SDk", + publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 1, + isSettingAccessCodeAllowed = false, + isSettingPasscodeAllowed = true, + isResettingUserCodesAllowed = true, + isLinkedTerminalEnabled = true, + isBackupAllowed = true, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = true, + isHDWalletAllowed = false, + isKeysImportAllowed = false, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = true, + isPasscodeSet = false, + supportedCurves = listOf(EllipticCurve.Secp256k1), + wallets = listOf( + CardDTO.Wallet( + publicKey = byteArrayOf(4, -45, -28, -111, 109, -9, -113, 17, 107, -45, 43, -55, -126, 103, -70, -13, 79, -103, -59, -69, 83, -47, -37, -49, -18, -65, 55, 6, 121, 17, 54, 32, 105, 2, 25, -101, -118, 91, 109, 49, 99, -42, -113, 105, 21, 109, 66, -7, -57, 55, 25, -37, 63, 16, 22, -118, 121, -127, -13, 47, -89, -4, -38, -65, -27), + chainCode = null, + curve = EllipticCurve.Secp256k1, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = 1000000, + index = 0, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = null, + isImported = false, + ), + ), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = null, + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Wallet, + walletData = WalletData( + blockchain = "BTC", + token = null, + ), + secondTwinPublicKey = null, + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + ) + + override val extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110), + chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ) + + override val successResponse = SuccessResponse(cardId = "CB37000000000002") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + primaryCard = primaryCard, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = error("Available only for Wallet 2") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/ShibaMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/ShibaMockContent.kt new file mode 100644 index 0000000000..9d3449a462 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/ShibaMockContent.kt @@ -0,0 +1,270 @@ +package com.tangem.tap.domain.sdk.mocks.content + +import com.tangem.common.SuccessResponse +import com.tangem.common.card.* +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.tap.domain.sdk.mocks.MockContent +import java.util.Date + +object ShibaMockContent : MockContent { + + private val primaryCard = PrimaryCard( + cardId = "AF02010000000002", + batchId = "AF02", + cardPublicKey = byteArrayOf(2, 48, 4, 54, -34, -33, 22, 53, -92, 8, 72, 50, 69, 1, 105, -53, 9, -126, -23, -61, -62, -85, -24, -112, -32, 76, -88, -66, -25, -83, -70, -95, 90), + linkingKey = byteArrayOf( + 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, + -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, + ), + existingWalletsCount = 3, isHDWalletAllowed = true, + issuer = Card.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34), + ), + manufacturer = Card.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1743759687), + signature = byteArrayOf(44, 8, 116, 111, -57, 20, -93, 101, -104, 97, -104, -15, 58, -8, 41, -53, 95, 99, 107, 47, -29, -118, 41, 64, 22, 94, 33, -81, 114, -47, 10, -16, 97, 14, 94, -36, -82, -108, 74, -9, 35, -38, 66, 67, -116, -55, 65, -30, -58, -33, 31, 120, -45, 42, -3, -120, -74, -97, 97, -102, -13, -28, 29, -41), + ), + walletCurves = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519, EllipticCurve.Bls12381G2Aug), + firmwareVersion = FirmwareVersion( + major = 6, + minor = 21, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + isKeysImportAllowed = false, + certificate = null, + ) + + override val cardDto = CardDTO( + cardId = "AF02010000000002", + batchId = "AF02", + cardPublicKey = byteArrayOf(2, 48, 4, 54, -34, -33, 22, 53, -92, 8, 72, 50, 69, 1, 105, -53, 9, -126, -23, -61, -62, -85, -24, -112, -32, 76, -88, -66, -25, -83, -70, -95, 90), + firmwareVersion = CardDTO.FirmwareVersion( + major = 6, + minor = 21, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM SDK", + manufactureDate = Date(1649635200000), + signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66), + ), + issuer = CardDTO.Issuer( + name = "TANGEM", + publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 20, + isSettingAccessCodeAllowed = true, + isSettingPasscodeAllowed = false, + isResettingUserCodesAllowed = true, + isLinkedTerminalEnabled = true, + isBackupAllowed = true, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = true, + isHDWalletAllowed = true, + isKeysImportAllowed = true, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = true, + isPasscodeSet = false, + supportedCurves = listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Secp256r1, + EllipticCurve.Bls12381G2, + EllipticCurve.Bls12381G2Aug, + EllipticCurve.Bls12381G2Pop, + EllipticCurve.Bip0340, + ), + wallets = listOf( + CardDTO.Wallet( + publicKey = byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + chainCode = byteArrayOf(74, -90, -85, -117, -116, -78, 98, 63, 83, 12, 43, -69, 43, -54, -119, -62, 80, 107, -127, 53, 73, 108, 114, -102, 81, -123, 68, 92, 65, -25, 107, 86), + curve = EllipticCurve.Secp256k1, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 0, + hasBackup = false, + derivedKeys = mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + ), + DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), + ), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14), + ), + isImported = false, + ), + CardDTO.Wallet( + publicKey = byteArrayOf(-123, -48, -77, -113, -63, -124, -18, 36, -14, -127, 104, 89, 48, 103, 69, -12, 107, -103, 64, -2, 97, 126, -78, -99, -9, -72, 94, -68, 30, 43, 38, 42), + chainCode = byteArrayOf(104, 109, 56, -52, 125, -119, -49, -51, -46, -122, -111, 75, 51, 103, 15, 25, -101, 72, -101, 101, -128, -2, -51, 3, 74, 62, -49, -6, -95, 91, -104, -58), + curve = EllipticCurve.Ed25519, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 1, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109), + chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6), + ), + isImported = false, + ), + ), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = CardDTO.BackupStatus.Active(cardCount = 1), + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Wallet, + walletData = null, + secondTwinPublicKey = null, + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + ) + + override val extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110), + chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ) + + override val successResponse = SuccessResponse(cardId = "AF02010000000002") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + primaryCard = primaryCard, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = error("Available only for Wallet 2") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/ShibaNoBackupMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/ShibaNoBackupMockContent.kt new file mode 100644 index 0000000000..fc03dab84c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/ShibaNoBackupMockContent.kt @@ -0,0 +1,270 @@ +package com.tangem.tap.domain.sdk.mocks.content + +import com.tangem.common.SuccessResponse +import com.tangem.common.card.* +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.tap.domain.sdk.mocks.MockContent +import java.util.Date + +object ShibaNoBackupMockContent : MockContent { + + private val primaryCard = PrimaryCard( + cardId = "AF02010000000002", + batchId = "AF02", + cardPublicKey = byteArrayOf(2, 48, 4, 54, -34, -33, 22, 53, -92, 8, 72, 50, 69, 1, 105, -53, 9, -126, -23, -61, -62, -85, -24, -112, -32, 76, -88, -66, -25, -83, -70, -95, 90), + linkingKey = byteArrayOf( + 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, + -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, + ), + existingWalletsCount = 3, isHDWalletAllowed = true, + issuer = Card.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34), + ), + manufacturer = Card.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1743759687), + signature = byteArrayOf(44, 8, 116, 111, -57, 20, -93, 101, -104, 97, -104, -15, 58, -8, 41, -53, 95, 99, 107, 47, -29, -118, 41, 64, 22, 94, 33, -81, 114, -47, 10, -16, 97, 14, 94, -36, -82, -108, 74, -9, 35, -38, 66, 67, -116, -55, 65, -30, -58, -33, 31, 120, -45, 42, -3, -120, -74, -97, 97, -102, -13, -28, 29, -41), + ), + walletCurves = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519, EllipticCurve.Bls12381G2Aug), + firmwareVersion = FirmwareVersion( + major = 6, + minor = 21, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + isKeysImportAllowed = false, + certificate = null, + ) + + override val cardDto = CardDTO( + cardId = "AF02010000000002", + batchId = "AF02", + cardPublicKey = byteArrayOf(2, 48, 4, 54, -34, -33, 22, 53, -92, 8, 72, 50, 69, 1, 105, -53, 9, -126, -23, -61, -62, -85, -24, -112, -32, 76, -88, -66, -25, -83, -70, -95, 90), + firmwareVersion = CardDTO.FirmwareVersion( + major = 6, + minor = 21, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM SDK", + manufactureDate = Date(1649635200000), + signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66), + ), + issuer = CardDTO.Issuer( + name = "TANGEM", + publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 20, + isSettingAccessCodeAllowed = true, + isSettingPasscodeAllowed = false, + isResettingUserCodesAllowed = true, + isLinkedTerminalEnabled = true, + isBackupAllowed = true, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = true, + isHDWalletAllowed = true, + isKeysImportAllowed = true, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = true, + isPasscodeSet = false, + supportedCurves = listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Secp256r1, + EllipticCurve.Bls12381G2, + EllipticCurve.Bls12381G2Aug, + EllipticCurve.Bls12381G2Pop, + EllipticCurve.Bip0340, + ), + wallets = listOf( + CardDTO.Wallet( + publicKey = byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + chainCode = byteArrayOf(74, -90, -85, -117, -116, -78, 98, 63, 83, 12, 43, -69, 43, -54, -119, -62, 80, 107, -127, 53, 73, 108, 114, -102, 81, -123, 68, 92, 65, -25, 107, 86), + curve = EllipticCurve.Secp256k1, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 0, + hasBackup = false, + derivedKeys = mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + ), + DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + ), + ), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + chainCode = byteArrayOf(80, 13, -8, -108, -35, 116, -92, 125, -65, -28, 85, 72, -113, 59, 83, 13, 5, -83, -102, 123, 124, -22, 94, 108, -71, 95, 65, -2, 38, 38, -108, 14), + ), + isImported = false, + ), + CardDTO.Wallet( + publicKey = byteArrayOf(-123, -48, -77, -113, -63, -124, -18, 36, -14, -127, 104, 89, 48, 103, 69, -12, 107, -103, 64, -2, 97, 126, -78, -99, -9, -72, 94, -68, 30, 43, 38, 42), + chainCode = byteArrayOf(104, 109, 56, -52, 125, -119, -49, -51, -46, -122, -111, 75, 51, 103, 15, 25, -101, 72, -101, 101, -128, -2, -51, 3, 74, 62, -49, -6, -95, 91, -104, -58), + curve = EllipticCurve.Ed25519, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 1, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(-65, -53, -62, -12, -57, -32, -38, -9, -128, -52, -83, -61, 73, 39, 41, 15, -74, -97, 38, 52, -101, 63, 74, -56, -20, 15, 57, -127, 114, -93, -17, -109), + chainCode = byteArrayOf(-81, 15, 125, 28, -115, -22, 87, 81, 87, -123, -25, -74, 86, 2, 1, 110, -115, 65, -110, 63, -64, 83, -93, -97, -104, 123, 12, -26, 94, 27, 84, -6), + ), + isImported = false, + ), + ), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = CardDTO.BackupStatus.NoBackup, + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Wallet, + walletData = null, + secondTwinPublicKey = null, + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + ) + + override val extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110), + chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ) + + override val successResponse = SuccessResponse(cardId = "AF02010000000002") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + primaryCard = primaryCard, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = error("Available only for Wallet 2") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/ShibaNoBackupNoWalletsMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/ShibaNoBackupNoWalletsMockContent.kt new file mode 100644 index 0000000000..a1b1b15b20 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/ShibaNoBackupNoWalletsMockContent.kt @@ -0,0 +1,223 @@ +package com.tangem.tap.domain.sdk.mocks.content + +import com.tangem.common.SuccessResponse +import com.tangem.common.card.Card +import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.EncryptionMode +import com.tangem.common.card.FirmwareVersion +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.tap.domain.sdk.mocks.MockContent +import java.util.Date + +object ShibaNoBackupNoWalletsMockContent : MockContent { + + private val primaryCard = PrimaryCard( + cardId = "AF02010000000002", + batchId = "AF02", + cardPublicKey = byteArrayOf(2, 48, 4, 54, -34, -33, 22, 53, -92, 8, 72, 50, 69, 1, 105, -53, 9, -126, -23, -61, -62, -85, -24, -112, -32, 76, -88, -66, -25, -83, -70, -95, 90), + linkingKey = byteArrayOf( + 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, + -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, + ), + existingWalletsCount = 3, isHDWalletAllowed = true, + issuer = Card.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34), + ), + manufacturer = Card.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1743759687), + signature = byteArrayOf(44, 8, 116, 111, -57, 20, -93, 101, -104, 97, -104, -15, 58, -8, 41, -53, 95, 99, 107, 47, -29, -118, 41, 64, 22, 94, 33, -81, 114, -47, 10, -16, 97, 14, 94, -36, -82, -108, 74, -9, 35, -38, 66, 67, -116, -55, 65, -30, -58, -33, 31, 120, -45, 42, -3, -120, -74, -97, 97, -102, -13, -28, 29, -41), + ), + walletCurves = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519, EllipticCurve.Bls12381G2Aug), + firmwareVersion = FirmwareVersion( + major = 6, + minor = 21, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + isKeysImportAllowed = false, + certificate = null, + ) + + override val cardDto = CardDTO( + cardId = "AF02010000000002", + batchId = "AF02", + cardPublicKey = byteArrayOf(2, 48, 4, 54, -34, -33, 22, 53, -92, 8, 72, 50, 69, 1, 105, -53, 9, -126, -23, -61, -62, -85, -24, -112, -32, 76, -88, -66, -25, -83, -70, -95, 90), + firmwareVersion = CardDTO.FirmwareVersion( + major = 6, + minor = 21, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM SDK", + manufactureDate = Date(1649635200000), + signature = byteArrayOf(-20, 98, -101, 94, -23, 73, 122, -21, 74, 76, 79, -55, -102, -62, -30, 44, -38, 118, 75, 121, -36, 118, -62, 60, -38, -63, 33, -14, -98, -69, 112, 22, 48, -43, 47, 65, -61, -56, 38, -94, -45, 44, 95, -22, 6, -31, -40, -25, 42, 38, 94, -99, 71, 98, -33, 27, -102, 30, 52, -7, 51, -27, 84, 66), + ), + issuer = CardDTO.Issuer( + name = "TANGEM", + publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 20, + isSettingAccessCodeAllowed = true, + isSettingPasscodeAllowed = false, + isResettingUserCodesAllowed = true, + isLinkedTerminalEnabled = true, + isBackupAllowed = true, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = true, + isHDWalletAllowed = true, + isKeysImportAllowed = true, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = true, + isPasscodeSet = false, + supportedCurves = listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Secp256r1, + EllipticCurve.Bls12381G2, + EllipticCurve.Bls12381G2Aug, + EllipticCurve.Bls12381G2Pop, + EllipticCurve.Bip0340, + ), + wallets = emptyList(), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = CardDTO.BackupStatus.NoBackup, + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Wallet, + walletData = null, + secondTwinPublicKey = null, + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + ) + + override val extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110), + chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ) + + override val successResponse = SuccessResponse(cardId = "AF02010000000002") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + primaryCard = primaryCard, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = error("Available only for Wallet 2") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/TwinsMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/TwinsMockContent.kt new file mode 100644 index 0000000000..bdb96fa680 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/TwinsMockContent.kt @@ -0,0 +1,208 @@ +package com.tangem.tap.domain.sdk.mocks.content + +import com.tangem.common.SuccessResponse +import com.tangem.common.card.* +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.tap.domain.sdk.mocks.MockContent +import java.util.Date + +object TwinsMockContent : MockContent { + + private val primaryCard = PrimaryCard( + cardId = "CB64000000015006", + batchId = "CB64", + cardPublicKey = byteArrayOf(4, -122, 60, 93, 121, 108, 26, 78, -33, 36, 112, 61, -4, 108, 82, 6, 47, -50, 109, 37, -109, 58, -102, 3, 50, 127, -123, -68, -96, 18, -78, 56, -50, 102, -112, 51, -25, 96, -69, 97, 31, 35, -68, 22, 38, 61, 60, -97, -69, -51, -62, -37, -63, 91, 127, -103, 103, -39, 85, 85, 85, 92, -70, -58, 109), + linkingKey = byteArrayOf( + 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, + -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, + ), + existingWalletsCount = 3, isHDWalletAllowed = false, + issuer = Card.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(4, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34, -11, 83, -57, -34, -17, -75, 79, -45, -44, -13, 97, -47, 78, 109, -61, -15, 27, 125, 78, -95, -125, 37, 10, 96, 114, 14, -67, -7, -31, 16, -51, 38), + ), + manufacturer = Card.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1743759687), + signature = byteArrayOf(89, -15, -31, 86, 117, 59, -27, -111, 33, 23, -44, 15, -55, -46, -52, 63, 66, 41, -73, -22, 4, -26, 96, -33, -91, 28, 111, 55, -105, -83, -19, -10, 39, -88, 47, 60, 41, -71, 81, 50, -1, -121, 86, -48, -93, 103, -42, 12, -3, -126, -47, -79, 44, 115, 126, 8, -63, 10, 77, 2, 107, -87, -54, -65), + ), + walletCurves = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519, EllipticCurve.Bls12381G2Aug), + firmwareVersion = FirmwareVersion( + major = 3, + minor = 38, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + isKeysImportAllowed = false, + certificate = null, + ) + + override val cardDto = CardDTO( + cardId = "CB64000000015006", + batchId = "CB64", + cardPublicKey = byteArrayOf(2, 48, 4, 54, -34, -33, 22, 53, -92, 8, 72, 50, 69, 1, 105, -53, 9, -126, -23, -61, -62, -85, -24, -112, -32, 76, -88, -66, -25, -83, -70, -95, 90), + firmwareVersion = CardDTO.FirmwareVersion( + major = 3, + minor = 38, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1649635200000), + signature = byteArrayOf(89, -15, -31, 86, 117, 59, -27, -111, 33, 23, -44, 15, -55, -46, -52, 63, 66, 41, -73, -22, 4, -26, 96, -33, -91, 28, 111, 55, -105, -83, -19, -10, 39, -88, 47, 60, 41, -71, 81, 50, -1, -121, 86, -48, -93, 103, -42, 12, -3, -126, -47, -79, 44, 115, 126, 8, -63, 10, 77, 2, 107, -87, -54, -65), + ), + issuer = CardDTO.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(4, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34, -11, 83, -57, -34, -17, -75, 79, -45, -44, -13, 97, -47, 78, 109, -61, -15, 27, 125, 78, -95, -125, 37, 10, 96, 114, 14, -67, -7, -31, 16, -51, 38), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 1, + isSettingAccessCodeAllowed = true, + isSettingPasscodeAllowed = true, + isResettingUserCodesAllowed = true, + isLinkedTerminalEnabled = true, + isBackupAllowed = false, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = true, + isHDWalletAllowed = false, + isKeysImportAllowed = false, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = false), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = false, + isPasscodeSet = null, + supportedCurves = listOf( + EllipticCurve.Secp256k1, + ), + wallets = listOf( + CardDTO.Wallet( + publicKey = byteArrayOf(4, 42, -7, -17, 16, -16, -85, 8, 70, 36, -114, 18, 45, -126, 101, -89, 22, 96, 53, -107, 61, -109, -118, 83, 116, 100, -79, -90, 119, 123, -67, 23, -76, -49, -124, 71, 61, -50, -91, -18, -100, -34, 93, -48, 36, -82, -28, 45, 125, -3, 56, 8, 68, -40, 40, -34, -112, -67, -43, 35, 84, -106, 20, 62, -79), + chainCode = null, + curve = EllipticCurve.Secp256k1, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 1, + remainingSignatures = 999999, + index = 0, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = null, + isImported = false, + ), + ), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = null, + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Twins, + walletData = WalletData( + blockchain = "BTC", + token = null, + ), + secondTwinPublicKey = "041535EF77F57A7E7C1541858E5BD3C096A81CEB39FCC72FB9354BBC55FEF86B3C2D5A44750FABE59D268BEC0CB1BAC854A7678F48A26BD355CE39FE197457E2FF", + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = mapOf( + ByteArrayKey( + byteArrayOf(2, -74, 50, 45, -105, 119, 115, 87, -7, -110, 33, 10, -41, -9, -41, 1, 63, -73, 31, -77, 86, 66, -47, -31, 16, 32, 63, 122, 87, 94, 41, 68, 125), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + ) + + override val extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110), + chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ) + + override val successResponse = SuccessResponse(cardId = "CB61000000001264") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = emptyMap(), + primaryCard = primaryCard, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = error("Available only for Wallet 2") + + override val createFirstTwinResponse: CreateWalletResponse = CreateWalletResponse( + cardId = "CB64000000015006", + wallet = CardWallet( + publicKey = byteArrayOf(4, 42, -7, -17, 16, -16, -85, 8, 70, 36, -114, 18, 45, -126, 101, -89, 22, 96, 53, -107, 61, -109, -118, 83, 116, 100, -79, -90, 119, 123, -67, 23, -76, -49, -124, 71, 61, -50, -91, -18, -100, -34, 93, -48, 36, -82, -28, 45, 125, -3, 56, 8, 68, -40, 40, -34, -112, -67, -43, 35, 84, -106, 20, 62, -79), + chainCode = null, + curve = EllipticCurve.Secp256k1, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 1, + remainingSignatures = 999999, + index = 0, + isImported = false, + hasBackup = false, + derivedKeys = emptyMap(), + ), + ) + + override val createSecondTwinResponse: CreateWalletResponse = CreateWalletResponse( + cardId = "CB65000000015005", + wallet = CardWallet( + publicKey = byteArrayOf(4, 21, 53, -17, 119, -11, 122, 126, 124, 21, 65, -123, -114, 91, -45, -64, -106, -88, 28, -21, 57, -4, -57, 47, -71, 53, 75, -68, 85, -2, -8, 107, 60, 45, 90, 68, 117, 15, -85, -27, -99, 38, -117, -20, 12, -79, -70, -56, 84, -89, 103, -113, 72, -94, 107, -45, 85, -50, 57, -2, 25, 116, 87, -30, -1), + chainCode = null, + curve = EllipticCurve.Secp256k1, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 1, + remainingSignatures = 999999, + index = 0, + isImported = false, + hasBackup = false, + derivedKeys = emptyMap(), + ), + ) + + override val finalizeTwinResponse: ScanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Twins, + walletData = WalletData( + blockchain = "BTC", + token = null, + ), + secondTwinPublicKey = "041535EF77F57A7E7C1541858E5BD3C096A81CEB39FCC72FB9354BBC55FEF86B3C2D5A44750FABE59D268BEC0CB1BAC854A7678F48A26BD355CE39FE197457E2FF", + derivedKeys = emptyMap(), + primaryCard = primaryCard, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt index 9b8fcc752f..e9204cdc06 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt @@ -20,20 +20,20 @@ import java.util.Date object Wallet2MockContent : MockContent { private val primaryCard = PrimaryCard( - cardId = "AF10000000981426", - batchId = "AF10", - cardPublicKey = byteArrayOf(3, 7, 80, -118, 6, 77, -15, -22, 107, 105, -64, 103, 77, -79, -102, 106, 46, 84, 21, -34, 47, -74, -56, 124, 17, -49, -29, 76, 84, 59, 50, -15, -52), + cardId = "AF05888888880018", + batchId = "AF05", + cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5), linkingKey = byteArrayOf( // 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, ), existingWalletsCount = 5, isHDWalletAllowed = true, issuer = Card.Issuer( - name = "TANGEM 2.0", - publicKey = byteArrayOf(2, -120, 89, -52, -60, 36, -73, -17, 103, 107, -110, -36, 3, 110, -122, 72, 43, -38, 8, 30, -50, 25, -23, -17, 38, 94, 5, -112, -20, 9, 54, -24, -32), + name = "TANGEM SDK", + publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34), ), manufacturer = Card.Manufacturer( - name = "TANGEM SDK", + name = "TANGEM", manufactureDate = Date(1743759687), signature = byteArrayOf(), ), @@ -58,9 +58,9 @@ object Wallet2MockContent : MockContent { ) override val cardDto = CardDTO( - cardId = "AF10000000981426", - batchId = "AF10", - cardPublicKey = byteArrayOf(3, 7, 80, -118, 6, 77, -15, -22, 107, 105, -64, 103, 77, -79, -102, 106, 46, 84, 21, -34, 47, -74, -56, 124, 17, -49, -29, 76, 84, 59, 50, -15, -52), + cardId = "AF05888888880018", + batchId = "AF05", + cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5), firmwareVersion = CardDTO.FirmwareVersion( major = 6, minor = 33, @@ -70,11 +70,11 @@ object Wallet2MockContent : MockContent { manufacturer = CardDTO.Manufacturer( name = "TANGEM", manufactureDate = Date(1698094800000), - signature = byteArrayOf(71, -20, 9, 31, 25, 111, 61, 119, 109, -123, -63, 51, 58, -71, -44, 53, 57, 20, -16, 97, -87, -82, 1, -35, -48, 63, 77, -78, -89, -112, -27, 25, -10, 90, 7, -53, -84, -125, 112, 68, -14, -85, -100, -64, -115, 31, 42, 119, 87, -79, 127, 42, -87, -102, 13, -9, -10, -51, -29, -63, -1, -52, -117, 57), + signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3), ), issuer = CardDTO.Issuer( - name = "TANGEM 2.0", - publicKey = byteArrayOf(2, -120, 89, -52, -60, 36, -73, -17, 103, 107, -110, -36, 3, 110, -122, 72, 43, -38, 8, 30, -50, 25, -23, -17, 38, 94, 5, -112, -20, 9, 54, -24, -32), + name = "TANGEM SDK", + publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34), ), settings = CardDTO.Settings( securityDelay = 15000, @@ -87,7 +87,7 @@ object Wallet2MockContent : MockContent { supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), isFilesAllowed = true, isHDWalletAllowed = true, - isKeysImportAllowed = false, + isKeysImportAllowed = true, ), userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true), linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, @@ -105,11 +105,11 @@ object Wallet2MockContent : MockContent { ), wallets = listOf( CardDTO.Wallet( - publicKey = byteArrayOf(2, -114, -64, 120, -121, 11, -28, 89, -91, 114, 10, 84, -87, -36, 36, 19, -69, 95, 66, 14, 32, -35, -99, -67, 118, 51, 26, 71, -78, -36, 59, -126, -58), - chainCode = byteArrayOf(-47, -8, 74, 69, -1, 52, 10, -56, -85, 118, 56, 77, 125, -12, 85, -23, 42, -58, 99, 47, -87, -34, -83, 72, -122, -29, 88, -85, 46, -118, -26, 116), + publicKey = byteArrayOf(3, 17, 59, -102, -56, 66, 10, 36, 97, -106, -92, -117, -105, -88, -2, -3, -95, -75, -54, -2, 57, 27, -90, -19, 82, 103, -12, -52, -126, -105, -120, -55, -2), + chainCode = byteArrayOf(-34, -28, -28, -12, 80, -109, -90, -31, -74, 36, -78, -21, 39, -125, -39, -91, 63, 40, -26, 3, 66, 27, -62, -55, -97, 52, -65, -11, 26, -80, 33, -45), curve = EllipticCurve.Secp256k1, settings = CardWallet.Settings(isPermanent = false), - totalSignedHashes = 73, + totalSignedHashes = 0, remainingSignatures = null, index = 0, hasBackup = true, @@ -128,14 +128,14 @@ object Wallet2MockContent : MockContent { ), ), extendedPublicKey = ExtendedPublicKey( - publicKey = byteArrayOf(2, -114, -64, 120, -121, 11, -28, 89, -91, 114, 10, 84, -87, -36, 36, 19, -69, 95, 66, 14, 32, -35, -99, -67, 118, 51, 26, 71, -78, -36, 59, -126, -58), - chainCode = byteArrayOf(-47, -8, 74, 69, -1, 52, 10, -56, -85, 118, 56, 77, 125, -12, 85, -23, 42, -58, 99, 47, -87, -34, -83, 72, -122, -29, 88, -85, 46, -118, -26, 116), + publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123), + chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127), ), isImported = false, ), CardDTO.Wallet( - publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90), - chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11), + publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123), + chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127), curve = EllipticCurve.Ed25519, settings = CardWallet.Settings(isPermanent = false), totalSignedHashes = 0, @@ -150,7 +150,7 @@ object Wallet2MockContent : MockContent { isImported = false, ), CardDTO.Wallet( - publicKey = byteArrayOf(-94, 111, -89, -77, -62, -113, 16, -118, -46, -16, -20, 28, 53, -82, -109, 28, 99, -98, -54, 59, -3, 99, 16, -70, -73, 43, -6, 33, -53, -66, 76, -72, 6, -49, 83, -121, -122, 111, -111, -116, -119, 98, -94, -98, -121, -37, 20, -95), + publicKey = byteArrayOf(-118, -77, -109, 8, -119, 101, -91, 46, -44, 15, 52, -64, -92, 5, -102, 126, 52, 121, 63, -76, -54, 80, -82, -5, 31, -35, 105, -119, -96, -54, 38, 2, -6, 109, 117, -26, -47, -20, 108, 106, -69, 72, -37, -117, -30, -9, 104, -123), chainCode = null, curve = EllipticCurve.Bls12381G2Aug, settings = CardWallet.Settings(isPermanent = false), @@ -163,8 +163,8 @@ object Wallet2MockContent : MockContent { isImported = false, ), CardDTO.Wallet( - publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123), - chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112), + publicKey = byteArrayOf(17, -78, 53, 101, 96, -110, -10, -48, -84, 88, -64, 58, -26, -13, -2, -6, -25, 23, -79, -45, 58, 77, 52, -75, -123, 121, 32, 92, 84, -74, -111, -8), + chainCode = byteArrayOf(-110, 73, -124, -65, -1, -70, 64, -89, 95, -74, 15, 46, -110, 87, 15, -61, 120, -51, 111, 111, -45, 37, 123, -114, 65, -116, 21, 39, -77, 86, -3, -26), curve = EllipticCurve.Bip0340, settings = CardWallet.Settings(isPermanent = false), totalSignedHashes = 0, @@ -179,11 +179,11 @@ object Wallet2MockContent : MockContent { isImported = false, ), CardDTO.Wallet( - publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47), - chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89), + publicKey = byteArrayOf(-52, -92, 64, 108, -8, -100, 87, -86, -60, 18, -18, -81, 114, -84, 76, -24, 84, 52, -34, 79, -30, -66, -112, -55, -35, -119, 127, -109, 35, 18, -29, -25), + chainCode = byteArrayOf(94, -33, -94, -63, 40, -20, -26, 71, 111, 9, 87, -36, -42, -33, 40, -53, 85, 31, -36, -29, 65, -121, 2, -23, -119, -51, 114, -17, -39, -74, 23, -97), curve = EllipticCurve.Ed25519Slip0010, settings = CardWallet.Settings(isPermanent = false), - totalSignedHashes = 15, + totalSignedHashes = 0, remainingSignatures = null, index = 4, hasBackup = true, @@ -219,76 +219,76 @@ object Wallet2MockContent : MockContent { byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), ) to - ExtendedPublicKeysMap( - mapOf( - DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth - publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), - chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch - publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), - chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge - publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), - chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, ), ), + ), ), ) override val extendedPublicKey = ExtendedPublicKey( - publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110), - chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34), + publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8), + chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96), depth = 0, parentFingerprint = byteArrayOf(0, 0, 0, 0), childNumber = 0, ) - override val successResponse = SuccessResponse(cardId = "AF10000000981426") + override val successResponse = SuccessResponse(cardId = "AF05888888880018") override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( card = cardDto, derivedKeys = mapOf( ByteArrayKey( - byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5), ) to - ExtendedPublicKeysMap( - mapOf( - DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth - publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), - chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, ), ), + ), ), primaryCard = primaryCard, ) diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2NoBackupMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2NoBackupMockContent.kt new file mode 100644 index 0000000000..fd3fe78bbc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2NoBackupMockContent.kt @@ -0,0 +1,307 @@ +package com.tangem.tap.domain.sdk.mocks.content + +import com.tangem.common.SuccessResponse +import com.tangem.common.card.* +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.tap.domain.sdk.mocks.MockContent +import java.util.Date + +object Wallet2NoBackupMockContent : MockContent { + + private val primaryCard = PrimaryCard( + cardId = "AF05888888880018", + batchId = "AF05", + cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5), + linkingKey = byteArrayOf( // + 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, + -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, + ), + existingWalletsCount = 5, isHDWalletAllowed = true, + issuer = Card.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34), + ), + manufacturer = Card.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1743759687), + signature = byteArrayOf(), + ), + walletCurves = listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Bls12381G2Aug, + EllipticCurve.Secp256r1, + EllipticCurve.Ed25519Slip0010, + EllipticCurve.Bls12381G2, + EllipticCurve.Bls12381G2Pop, + EllipticCurve.Bip0340, + ), + firmwareVersion = FirmwareVersion( + major = 6, + minor = 33, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + isKeysImportAllowed = false, + certificate = null, + ) + + override val cardDto = CardDTO( + cardId = "AF05888888880018", + batchId = "AF05", + cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5), + firmwareVersion = CardDTO.FirmwareVersion( + major = 6, + minor = 33, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1698094800000), + signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3), + ), + issuer = CardDTO.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 20, + isSettingAccessCodeAllowed = true, + isSettingPasscodeAllowed = true, + isResettingUserCodesAllowed = false, + isLinkedTerminalEnabled = true, + isBackupAllowed = true, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = true, + isHDWalletAllowed = true, + isKeysImportAllowed = true, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = true, + isPasscodeSet = false, + supportedCurves = listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Bls12381G2Aug, + EllipticCurve.Secp256r1, + EllipticCurve.Ed25519Slip0010, + EllipticCurve.Bls12381G2, + EllipticCurve.Bls12381G2Pop, + EllipticCurve.Bip0340, + ), + wallets = listOf( + CardDTO.Wallet( + publicKey = byteArrayOf(3, 17, 59, -102, -56, 66, 10, 36, 97, -106, -92, -117, -105, -88, -2, -3, -95, -75, -54, -2, 57, 27, -90, -19, 82, 103, -12, -52, -126, -105, -120, -55, -2), + chainCode = byteArrayOf(-34, -28, -28, -12, 80, -109, -90, -31, -74, 36, -78, -21, 39, -125, -39, -91, 63, 40, -26, 3, 66, 27, -62, -55, -97, 52, -65, -11, 26, -80, 33, -45), + curve = EllipticCurve.Secp256k1, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 0, + hasBackup = false, + derivedKeys = mapOf( + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, 55, -114, -94, -73, -61, -50, 51, -115, 55, -79, 63, -96, -44, -64, -24, -36, -122, -123, -38, 81, -15, -127, -97, -42, 72, -85, -62, -98, 46, 119, 16, -55), + chainCode = byteArrayOf(31, 17, 71, -28, -29, 17, 72, -29, -98, 112, 31, -8, 72, -75, 4, -11, 60, -100, 9, 35, 58, -42, -38, 96, -71, -68, 24, -119, -43, -18, -122, 72), + ), + DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -26, 103, 96, 112, 127, -125, 0, 7, 53, 30, -12, -82, 45, 14, 107, -9, 126, 75, 104, -67, -49, 35, -12, -82, -90, 101, -101, 125, -76, 88, -54, 99), + chainCode = byteArrayOf(-42, 36, 97, 65, -64, -113, 76, -91, -9, 11, 89, 123, -9, -3, 21, 103, -113, -60, 48, -31, -34, 108, 111, -38, -110, -80, 109, 17, -29, 2, 45, -71), + ), + DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -96, -3, -83, 101, 63, -25, -125, -4, -65, -42, -56, 24, -52, 118, -11, -104, -105, 40, -59, 20, -109, -97, 29, -95, -6, -80, 2, 67, 103, -80, -22, -94), + chainCode = byteArrayOf(-125, 27, -91, 38, -66, 109, -92, 16, -37, 93, 107, -29, -128, -1, 115, -64, 108, -63, 17, 27, 58, 78, -2, 39, 88, -39, 44, 89, 32, -38, -16, -38), + ), + ), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123), + chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127), + ), + isImported = false, + ), + CardDTO.Wallet( + publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123), + chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127), + curve = EllipticCurve.Ed25519, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 1, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90), + chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11), + ), + isImported = false, + ), + CardDTO.Wallet( + publicKey = byteArrayOf(-118, -77, -109, 8, -119, 101, -91, 46, -44, 15, 52, -64, -92, 5, -102, 126, 52, 121, 63, -76, -54, 80, -82, -5, 31, -35, 105, -119, -96, -54, 38, 2, -6, 109, 117, -26, -47, -20, 108, 106, -69, 72, -37, -117, -30, -9, 104, -123), + chainCode = null, + curve = EllipticCurve.Bls12381G2Aug, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 1, + remainingSignatures = null, + index = 2, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = null, + isImported = false, + ), + CardDTO.Wallet( + publicKey = byteArrayOf(17, -78, 53, 101, 96, -110, -10, -48, -84, 88, -64, 58, -26, -13, -2, -6, -25, 23, -79, -45, 58, 77, 52, -75, -123, 121, 32, 92, 84, -74, -111, -8), + chainCode = byteArrayOf(-110, 73, -124, -65, -1, -70, 64, -89, 95, -74, 15, 46, -110, 87, 15, -61, 120, -51, 111, 111, -45, 37, 123, -114, 65, -116, 21, 39, -77, 86, -3, -26), + curve = EllipticCurve.Bip0340, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 3, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123), + chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112), + ), + isImported = false, + ), + CardDTO.Wallet( + publicKey = byteArrayOf(-52, -92, 64, 108, -8, -100, 87, -86, -60, 18, -18, -81, 114, -84, 76, -24, 84, 52, -34, 79, -30, -66, -112, -55, -35, -119, 127, -109, 35, 18, -29, -25), + chainCode = byteArrayOf(94, -33, -94, -63, 40, -20, -26, 71, 111, 9, 87, -36, -42, -33, 40, -53, 85, 31, -36, -29, 65, -121, 2, -23, -119, -51, 114, -17, -39, -74, 23, -97), + curve = EllipticCurve.Ed25519Slip0010, + settings = CardWallet.Settings(isPermanent = false), + totalSignedHashes = 0, + remainingSignatures = null, + index = 4, + hasBackup = false, + derivedKeys = emptyMap(), + extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47), + chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89), + ), + isImported = false, + ), + ), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = CardDTO.BackupStatus.NoBackup, + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Wallet2, + walletData = null, + secondTwinPublicKey = null, + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = mapOf( + ByteArrayKey( + byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + ) + + override val extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8), + chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ) + + override val successResponse = SuccessResponse(cardId = "AF05888888880018") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = mapOf( + ByteArrayKey( + byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + primaryCard = primaryCard, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = TODO("Not yet implemented") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2NoBackupNoWalletsMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2NoBackupNoWalletsMockContent.kt new file mode 100644 index 0000000000..682794f29b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2NoBackupNoWalletsMockContent.kt @@ -0,0 +1,219 @@ +package com.tangem.tap.domain.sdk.mocks.content + +import com.tangem.common.SuccessResponse +import com.tangem.common.card.Card +import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.EncryptionMode +import com.tangem.common.card.FirmwareVersion +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.operations.attestation.Attestation +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.tap.domain.sdk.mocks.MockContent +import java.util.Date + +object Wallet2NoBackupNoWalletsMockContent : MockContent { + + private val primaryCard = PrimaryCard( + cardId = "AF05888888880018", + batchId = "AF05", + cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5), + linkingKey = byteArrayOf( // + 2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121, + -57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91, + ), + existingWalletsCount = 5, isHDWalletAllowed = true, + issuer = Card.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34), + ), + manufacturer = Card.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1743759687), + signature = byteArrayOf(), + ), + walletCurves = listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Bls12381G2Aug, + EllipticCurve.Secp256r1, + EllipticCurve.Ed25519Slip0010, + EllipticCurve.Bls12381G2, + EllipticCurve.Bls12381G2Pop, + EllipticCurve.Bip0340, + ), + firmwareVersion = FirmwareVersion( + major = 6, + minor = 33, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + isKeysImportAllowed = false, + certificate = null, + ) + + override val cardDto = CardDTO( + cardId = "AF05888888880018", + batchId = "AF05", + cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5), + firmwareVersion = CardDTO.FirmwareVersion( + major = 6, + minor = 33, + patch = 0, + type = FirmwareVersion.FirmwareType.Release, + ), + manufacturer = CardDTO.Manufacturer( + name = "TANGEM", + manufactureDate = Date(1698094800000), + signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3), + ), + issuer = CardDTO.Issuer( + name = "TANGEM SDK", + publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34), + ), + settings = CardDTO.Settings( + securityDelay = 15000, + maxWalletsCount = 20, + isSettingAccessCodeAllowed = true, + isSettingPasscodeAllowed = true, + isResettingUserCodesAllowed = false, + isLinkedTerminalEnabled = true, + isBackupAllowed = true, + supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None), + isFilesAllowed = true, + isHDWalletAllowed = true, + isKeysImportAllowed = true, + ), + userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true), + linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None, + isAccessCodeSet = true, + isPasscodeSet = false, + supportedCurves = listOf( + EllipticCurve.Secp256k1, + EllipticCurve.Ed25519, + EllipticCurve.Bls12381G2Aug, + EllipticCurve.Secp256r1, + EllipticCurve.Ed25519Slip0010, + EllipticCurve.Bls12381G2, + EllipticCurve.Bls12381G2Pop, + EllipticCurve.Bip0340, + ), + wallets = emptyList(), + attestation = Attestation( + cardKeyAttestation = Attestation.Status.Verified, + walletKeysAttestation = Attestation.Status.Skipped, + firmwareAttestation = Attestation.Status.Skipped, + cardUniquenessAttestation = Attestation.Status.Skipped, + ), + backupStatus = CardDTO.BackupStatus.NoBackup, + ) + + override val scanResponse = ScanResponse( + card = cardDto, + productType = ProductType.Wallet2, + walletData = null, + secondTwinPublicKey = null, + derivedKeys = emptyMap(), + primaryCard = null, + ) + + override val derivationTaskResponse = DerivationTaskResponse( + entries = mapOf( + ByteArrayKey( + byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + ) + + override val extendedPublicKey = ExtendedPublicKey( + publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8), + chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ) + + override val successResponse = SuccessResponse(cardId = "AF05888888880018") + + override val createProductWalletTaskResponse = CreateProductWalletTaskResponse( + card = cardDto, + derivedKeys = mapOf( + ByteArrayKey( + byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5), + ) + to + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + ), + ), + ), + primaryCard = primaryCard, + ) + + override val importWalletResponse: CreateProductWalletTaskResponse + get() = TODO("Not yet implemented") + + override val createFirstTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val createSecondTwinResponse: CreateWalletResponse + get() = error("Available only for Twin") + + override val finalizeTwinResponse: ScanResponse + get() = error("Available only for Twin") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2WithSeedPhraseMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2WithSeedPhraseMockContent.kt index 06d7e7db26..d0625bd95a 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2WithSeedPhraseMockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2WithSeedPhraseMockContent.kt @@ -219,38 +219,38 @@ object Wallet2WithSeedPhraseMockContent : MockContent { byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), ) to - ExtendedPublicKeysMap( - mapOf( - DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth - publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), - chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch - publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), - chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge - publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), - chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, ), ), + ), ), ) @@ -271,24 +271,24 @@ object Wallet2WithSeedPhraseMockContent : MockContent { byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5), ) to - ExtendedPublicKeysMap( - mapOf( - DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth - publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), - chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, ), ), + ), ), primaryCard = primaryCard, ) diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt index 1db7cd1bb9..1ce637b289 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt @@ -168,59 +168,59 @@ object WalletMockContent : MockContent { byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), ) to - ExtendedPublicKeysMap( - mapOf( - DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth - publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), - chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch - publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), - chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/144'/0'/0/0") to ExtendedPublicKey( // xrp - publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), - chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge - publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), - chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( - publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), - chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/144'/0'/0/0") to ExtendedPublicKey( // xrp + publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121), + chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge + publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124), + chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey( + publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62), + chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, ), ), + ), ), ) @@ -241,24 +241,24 @@ object WalletMockContent : MockContent { byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5), ) to - ExtendedPublicKeysMap( - mapOf( - DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc - publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), - chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), - DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth - publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), - chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), - depth = 0, - parentFingerprint = byteArrayOf(0, 0, 0, 0), - childNumber = 0, - ), + ExtendedPublicKeysMap( + mapOf( + DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), + DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth + publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), + chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, ), ), + ), ), primaryCard = primaryCard, ) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt index 8d637b7c78..7e559abf8e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashesTask.kt @@ -42,7 +42,12 @@ class SignHashesTask( is CompletionResult.Failure -> { when { response.error is TangemSdkError.WalletNotFound && pairWalletPublicKey != null -> { - sign(session, pairWalletPublicKey, publicKey.derivationPath, callback) + sign( + session = session, + publicKey = pairWalletPublicKey, + derivationPath = publicKey.derivationPath, + callback = callback, + ) } else -> callback(CompletionResult.Failure(response.error)) } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index fba95394c0..ac172d1b9c 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -64,22 +64,28 @@ class CreateProductWalletTask( cardTypesResolver.isTangemTwins() -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") - else -> CreateWalletTangemWallet(mnemonic, passphrase, shouldReset, derivationStyleProvider, cardDto) + else -> CreateWalletTangemWallet( + mnemonic = mnemonic, + passphrase = passphrase, + shouldReset = shouldReset, + derivationStyleProvider = derivationStyleProvider, + cardDTO = cardDto, + ) } - commandProcessor.proceed(cardDto, session) { - when (it) { + commandProcessor.proceed(cardDto, session) { result -> + when (result) { is CompletionResult.Success -> { - val result = when (commandProcessor) { + val createProductWalletTaskResponse = when (commandProcessor) { is CreateWalletTangemWallet -> { - it.data as CreateProductWalletTaskResponse + result.data as CreateProductWalletTaskResponse } - else -> CreateProductWalletTaskResponse(card = session.environment.card!!) + else -> CreateProductWalletTaskResponse(card = requireNotNull(session.environment.card)) } - callback(CompletionResult.Success(result)) + callback(CompletionResult.Success(createProductWalletTaskResponse)) } - is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } @@ -156,7 +162,12 @@ private class CreateWalletTangemWallet( CreateWalletsTask(cardConfig.mandatoryCurves, mnemonic, passphrase).run(session) { result -> when (result) { is CompletionResult.Success -> { - checkIfAllWalletsCreated(card, session, result.data, callback) + checkIfAllWalletsCreated( + card = card, + session = session, + createResponse = result.data, + callback = callback, + ) } is CompletionResult.Failure -> { callback(CompletionResult.Failure(result.error)) @@ -210,12 +221,16 @@ private class CreateWalletTangemWallet( callback: (result: CompletionResult) -> Unit, ) { val resetCommand = ResetToFactorySettingsTask(allowsRequestAccessCodeFromRepository = false) - resetCommand.run(session) { - when (it) { + resetCommand.run(session) { result -> + when (result) { is CompletionResult.Success -> { - createMultiWallet(card, session, callback) + createMultiWallet( + card = card, + session = session, + callback = callback, + ) } - is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } @@ -228,17 +243,27 @@ private class CreateWalletTangemWallet( ) { when { card.settings.isBackupAllowed -> { - linkPrimaryCard(card, createWalletResponses, session, callback) + linkPrimaryCard( + card = card, + createWalletResponses = createWalletResponses, + session = session, + callback = callback, + ) } card.settings.isHDWalletAllowed -> { - deriveKeys(card, createWalletResponses, session, callback) + deriveKeys( + card = card, + createWalletResponses = createWalletResponses, + session = session, + callback = callback, + ) } else -> { callback( CompletionResult.Success( - CreateProductWalletTaskResponse(card = session.environment.card!!), + CreateProductWalletTaskResponse(card = requireNotNull(session.environment.card)), ), ) } @@ -247,7 +272,7 @@ private class CreateWalletTangemWallet( private fun linkPrimaryCard( card: CardDTO, - createWalletResponse: List, + createWalletResponses: List, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { @@ -257,14 +282,19 @@ private class CreateWalletTangemWallet( primaryCard = result.data when { card.settings.isHDWalletAllowed -> { - deriveKeys(card, createWalletResponse, session, callback) + deriveKeys( + card = card, + createWalletResponses = createWalletResponses, + session = session, + callback = callback, + ) } else -> { callback( CompletionResult.Success( CreateProductWalletTaskResponse( - card = session.environment.card!!, + card = requireNotNull(session.environment.card), primaryCard = primaryCard, ), ), @@ -282,13 +312,13 @@ private class CreateWalletTangemWallet( private fun deriveKeys( card: CardDTO, - createWalletResponse: List, + createWalletResponses: List, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { val map = mutableMapOf>() var isBlockchainsForCurvesExist = false - createWalletResponse.forEach { response -> + createWalletResponses.forEach { response -> val blockchainsForCurve = getBlockchains(response.cardId, card).filter { it.getSupportedCurves().contains(response.wallet.curve) } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt index c0ddcdeea3..628dbf9005 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt @@ -42,7 +42,7 @@ class CreateWalletsTask( callback: (result: CompletionResult) -> Unit, ) { val extendedPrivateKey = mnemonic?.let { - AnyMasterKeyFactory(mnemonic = it, passphrase = passphrase ?: "").makeMasterKey(curve) + AnyMasterKeyFactory(mnemonic = it, passphrase = passphrase.orEmpty()).makeMasterKey(curve) } CreateWalletTask(curve, extendedPrivateKey).run(session) { result -> when (result) { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt index fa255fdacb..54df91df2f 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt @@ -34,12 +34,10 @@ internal class DerivationsFinder( val userWalletId = UserWalletIdBuilder.card(card).build() ?: return emptySet() val derivationStyle = derivationStyleProvider.getDerivationStyle() - var blockchains = withContext(dispatchers.io) { + val blockchains = withContext(dispatchers.io) { getBlockchains(userWalletId) - } - - if (blockchains.isEmpty()) { - blockchains = if (DemoHelper.isDemoCardId(card.cardId)) { + }.ifEmpty { + if (DemoHelper.isDemoCardId(card.cardId)) { getDemoBlockchains(derivationStyle) } else { getDefaultBlockchains(derivationStyle) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt index 51ff0b2d80..c00639f1bd 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetToFactorySettingsTask.kt @@ -19,10 +19,15 @@ class ResetToFactorySettingsTask( } private fun deleteWallets(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val wallet = session.environment.card?.wallets?.lastOrNull().guard { - resetBackup(session, callback) - return - } + val wallet = session + .environment + .card + ?.wallets + ?.lastOrNull() + .guard { + resetBackup(session, callback) + return + } PurgeWalletCommand(wallet.publicKey).run(session) { result -> when (result) { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index df05ce2f7e..27bc6e7e13 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -111,13 +111,13 @@ internal class ScanProductTask( } private fun getErrorIfExcludedCard(cardDto: CardDTO, card: Card): TangemError? { - if (cardDto.isExcluded) return TapSdkError.CardForDifferentApp - if (cardDto.isNotSupportedInThatRelease) return TapSdkError.CardNotSupportedByRelease + if (cardDto.isExcluded) return TapSdkError.CardForDifferentApp() + if (cardDto.isNotSupportedInThatRelease) return TapSdkError.CardNotSupportedByRelease() // according ios decline card lower Ed25519Slip0010Available version and contains imported wallets if (card.firmwareVersion < FirmwareVersion.Ed25519Slip0010Available && card.wallets.any { it.isImported } ) { - return TapSdkError.CardNotSupportedByRelease + return TapSdkError.CardNotSupportedByRelease() } return null } @@ -253,12 +253,13 @@ private class ScanWalletProcessor( callback: (result: CompletionResult) -> Unit, ) { mainScope.launch { - val activationInProgress = store.inject(DaggerGraphState::cardRepository) + val isActivationInProgress = store.inject(DaggerGraphState::cardRepository) .isActivationInProgress(card.cardId) @Suppress("ComplexCondition") - if (card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty() && - activationInProgress + if (card.backupStatus == CardDTO.BackupStatus.NoBackup && + card.wallets.isNotEmpty() && + isActivationInProgress ) { StartPrimaryCardLinkingTask().run(session) { linkingResult -> when (linkingResult) { @@ -372,8 +373,8 @@ private class ScanTwinProcessor : ProductCommandProcessor { return@run } - val verified = TwinsHelper.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey) - val response = if (verified) { + val isVerified = TwinsHelper.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey) + val response = if (isVerified) { val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) val walletData = session.environment.walletData ScanResponse( diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt index 9dd041b2ea..547876b22f 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt @@ -153,9 +153,9 @@ class VisaCardActivationTask @AssistedInject constructor( val otpTaskDeferred = async { createOTP() } val dataToSign = dataToSignDeferred.await() - .getOrElse { + .getOrElse { error -> otpTaskDeferred.cancel() - return@coroutineScope CompletionResult.Failure(it) + return@coroutineScope CompletionResult.Failure(error) } otpTaskDeferred.await() diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt index 4fa773c18c..64fc98c01d 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt @@ -115,8 +115,8 @@ class VisaCustomerWalletApproveTask( extendedPublicKey = extendedPublicKey, ) - validationResult.onLeft { - callback(CompletionResult.Failure(it.tangemError)) + validationResult.onLeft { error -> + callback(CompletionResult.Failure(error.tangemError)) return } @@ -137,8 +137,8 @@ class VisaCustomerWalletApproveTask( val publicKey = findKeyWithoutDerivation( targetAddress = visaDataForApprove.targetAddress, card = CardDTO(card), - ).getOrElse { - callback(CompletionResult.Failure(it.tangemError)) + ).getOrElse { error -> + callback(CompletionResult.Failure(error.tangemError)) return } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index d55d7d7d32..b466017130 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -37,7 +37,7 @@ class CreateSecondTwinWalletTask( } if (!TwinsHelper.isTwinsCompatible(firstCardId, card.cardId)) { - callback(CompletionResult.Failure(IncompatibleTwinCard)) + callback(CompletionResult.Failure(IncompatibleTwinCard())) return } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt index 8d2c83906c..2b20696ee9 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt @@ -24,7 +24,7 @@ class FinalizeTwinTask( when (readResult) { is CompletionResult.Success -> ScanProductTask( - readResult.data, + card = readResult.data, derivationsFinder = null, visaCardScanHandler = null, visaCoroutineScope = null, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/IncompatibleTwinCard.kt b/app/src/main/java/com/tangem/tap/domain/twins/IncompatibleTwinCard.kt index 10afdb020a..a9c4f33eb4 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/IncompatibleTwinCard.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/IncompatibleTwinCard.kt @@ -4,7 +4,7 @@ import com.tangem.common.core.TangemError import com.tangem.tap.tangemSdkManager import com.tangem.wallet.R -object IncompatibleTwinCard : TangemError(code = 50005) { +class IncompatibleTwinCard : TangemError(code = 50005) { override var customMessage: String = tangemSdkManager.getString( R.string.twin_error_wrong_twin, ) diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 2a9dcc2d23..b503705f7e 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -41,7 +41,7 @@ class TwinCardsManager(card: CardDTO) { creatingWalletMessage: Message, ): CompletionResult { val response = tangemSdkManager.createSecondTwinWallet( - firstPublicKey = currentCardPublicKey!!, + firstPublicKey = requireNotNull(currentCardPublicKey), firstCardId = firstCardId, issuerKeys = getIssuerKeys(), preparingMessage = preparingMessage, @@ -58,7 +58,7 @@ class TwinCardsManager(card: CardDTO) { suspend fun complete(message: Message): CompletionResult { val response = tangemSdkManager.finalizeTwin( - secondCardPublicKey = secondCardPublicKey!!.hexToBytes(), + secondCardPublicKey = requireNotNull(secondCardPublicKey).hexToBytes(), issuerKeyPair = getIssuerKeys(), cardId = firstCardId, initialMessage = message, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt index 7eae636918..7dc2b0adff 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt @@ -21,8 +21,9 @@ class WriteProtectedIssuerDataTask( override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { SignHashCommand( - twinPublicKey.calculateSha256(), - session.environment.card!!.wallets.first().publicKey, + hash = twinPublicKey.calculateSha256(), + walletPublicKey = requireNotNull(session.environment.card) + .wallets.first().publicKey, ) .run(session) { signResult -> when (signResult) { @@ -31,12 +32,12 @@ class WriteProtectedIssuerDataTask( when (readResult) { is CompletionResult.Success -> { writeIssuerData( - twinPublicKey, - issuerKeys, - signResult.data.signature, - readResult.data, - session, - callback, + twinPublicKey = twinPublicKey, + issuerKeys = issuerKeys, + cardSignature = signResult.data.signature, + readResponse = readResult.data, + session = session, + callback = callback, ) } is CompletionResult.Failure -> callback( @@ -78,7 +79,7 @@ class WriteProtectedIssuerDataTask( ) WriteIssuerDataCommand( issuerData = data, - issuerDataSignature = signedByIssuer.finalizingSignature!!, + issuerDataSignature = requireNotNull(signedByIssuer.finalizingSignature), issuerDataCounter = counter, issuerPublicKey = issuerKeys.publicKey, ).run(session, callback) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 956ce71605..ae13a2ec94 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -57,12 +57,12 @@ internal class BiometricUserWalletsListManager( override val selectedUserWalletSync: UserWallet? get() = findSelectedUserWallet() - override val isLocked: Flow + override val lockedState: Flow get() = state .mapLatest { it.isLocked } .distinctUntilChanged() - override val isLockedSync: Boolean + override val isLocked: Boolean get() = state.value.isLocked override val hasUserWallets: Boolean @@ -103,7 +103,9 @@ internal class BiometricUserWalletsListManager( override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { if (state.value.selectedUserWalletId == userWalletId) { - return@catching findSelectedUserWallet()!! + return@catching requireNotNull(findSelectedUserWallet()) { + "Wallet is not found" + } } selectedUserWalletRepository.set(userWalletId) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 4ae134f570..f38d94f5c8 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -93,23 +93,23 @@ internal class GeneralUserWalletsListManager( override val walletsCount: Int get() = requireImplementation.walletsCount - override val isLocked: Flow + override val lockedState: Flow get() = implementation.transformLatest { impl -> if (impl == null) return@transformLatest if (impl is UserWalletsListManager.Lockable) { - emitAll(impl.isLocked) + emitAll(impl.lockedState) } else { error("RuntimeUserWalletsListManager is not lockable") } } - override val isLockedSync: Boolean + override val isLocked: Boolean get() { val impl = requireImplementation return if (impl is UserWalletsListManager.Lockable) { - impl.isLockedSync + impl.isLocked } else { error("RuntimeUserWalletsListManager is not lockable") } @@ -173,10 +173,10 @@ internal class GeneralUserWalletsListManager( } if (possibleManager == implementation.value) { - Timber.e("Switch to the same manager ${possibleManager::class.simpleName}") + Timber.e("Switch to the same manager ${possibleManager::class.simpleName.orEmpty()}") } - Timber.i("Switch to ${possibleManager::class.simpleName}") + Timber.i("Switch to ${possibleManager::class.simpleName.orEmpty()}") val previousManager = implementation.value implementation.value = copySelectedUserWallet( diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index 900c4bb7bf..31ad6301f0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -74,11 +74,13 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { ?.takeIf { it.walletId == userWalletId } ?: walletNotFound() - state.updateAndGet { prevState -> - prevState.copy( - userWallet = update(wallet), - ) - }.userWallet!! + requireNotNull( + state.updateAndGet { prevState -> + prevState.copy( + userWallet = update(wallet), + ) + }.userWallet, + ) { "User wallet is null after update" } } override suspend fun delete(userWalletIds: List): CompletionResult = clear() diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt index b4019602d9..bc5d0c26ac 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt @@ -21,6 +21,7 @@ internal data class UserWalletSensitiveInformation( val mobileWallets: List? = null, ) +@Suppress("BooleanPropertyNaming") @JsonClass(generateAdapter = true) internal data class UserWalletPublicInformation( // Common diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt index c4a0b716f7..92f7bb6ac9 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt @@ -125,7 +125,7 @@ internal class DefaultUserWalletsListRepository( // update the userWallets state and add if it doesn't exist updateWallets { currentWallets -> - val wallets = currentWallets ?: emptyList() + val wallets = currentWallets.orEmpty() if (wallets.any { it.walletId == userWallet.walletId }) { wallets.map { if (it.walletId == userWallet.walletId) userWallet else it } } else { @@ -332,12 +332,12 @@ internal class DefaultUserWalletsListRepository( raise(LockWalletsError.NothingToLock) } - updateWallets { - it?.map { - if (it.walletId !in unsecuredWalletIds) { - it.lock() + updateWallets { wallets -> + wallets?.map { wallet -> + if (wallet.walletId !in unsecuredWalletIds) { + wallet.lock() } else { - it + wallet } } } @@ -395,17 +395,17 @@ internal class DefaultUserWalletsListRepository( } private suspend fun hasBiometry(): Boolean { - val useBiometricAuthentication = appPreferencesStore.getSyncOrDefault( + val isBiometricAuthenticationUsed = appPreferencesStore.getSyncOrDefault( key = PreferencesKeys.USE_BIOMETRIC_AUTHENTICATION_KEY, default = false, ) - return tangemSdkManagerProvider.invoke().canUseBiometry && useBiometricAuthentication + return tangemSdkManagerProvider.invoke().canUseBiometry && isBiometricAuthenticationUsed } private fun updateWallets(block: (List?) -> List?) { - userWallets.update { - val updated = block(it) + userWallets.update { wallets -> + val updated = block(wallets) selectedUserWallet.update { currentSelected -> if (currentSelected == null) return@update null updated?.find { it.walletId == currentSelected.walletId } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletEncryptionKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletEncryptionKeysRepository.kt index 104cfc0945..faf5553d90 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletEncryptionKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletEncryptionKeysRepository.kt @@ -137,8 +137,7 @@ internal class UserWalletEncryptionKeysRepository( private suspend fun UserWalletEncryptionKey.encode(): ByteArray { return withContext(dispatchers.default) { - this@encode - .let(encryptionKeyAdapter::toJson) + encryptionKeyAdapter.toJson(this@encode) .encodeToByteArray(throwOnInvalidSequence = true) } } @@ -153,8 +152,7 @@ internal class UserWalletEncryptionKeysRepository( private suspend fun List.encode(): ByteArray { return withContext(dispatchers.default) { - this@encode - .let(userWalletsIdsListAdapter::toJson) + userWalletsIdsListAdapter.toJson(this@encode) .encodeToByteArray(throwOnInvalidSequence = true) } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 1a3d1c3b38..6f82167edd 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -164,8 +164,7 @@ internal class BiometricUserWalletsKeysRepository( private suspend fun UserWalletEncryptionKey.encode(): ByteArray { return withContext(Dispatchers.Default) { - this@encode - .let(encryptionKeyAdapter::toJson) + encryptionKeyAdapter.toJson(this@encode) .encodeToByteArray(throwOnInvalidSequence = true) } } @@ -180,8 +179,7 @@ internal class BiometricUserWalletsKeysRepository( private suspend fun List.encode(): ByteArray { return withContext(Dispatchers.Default) { - this@encode - .let(userWalletsIdsListAdapter::toJson) + userWalletsIdsListAdapter.toJson(this@encode) .encodeToByteArray(throwOnInvalidSequence = true) } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index a66ba5bdbb..c3e8f86072 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -80,8 +80,7 @@ internal class DefaultUserWalletsPublicInformationRepository( @JvmName("saveWithPublicInformation") private suspend fun save(publicInformation: List): CompletionResult = catching { withContext(Dispatchers.IO) { - publicInformation - .let(publicInformationAdapter::toJson) + publicInformationAdapter.toJson(publicInformation) .encodeToByteArray(throwOnInvalidSequence = true) .also { secureStorage.store(it, StorageKey.UserWalletPublicInformation.name) } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 23d1e3e069..f4a2c32083 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -110,18 +110,18 @@ internal class DefaultUserWalletsSensitiveInformationRepository( private suspend fun ByteArray.decodeToEncryptedSensitiveInformation(): Map? { return withContext(Dispatchers.Default) { - this@decodeToEncryptedSensitiveInformation - .decodeToString(throwOnInvalidSequence = true) - .let(encryptedSensitiveInformationMapAdapter::fromJson) + encryptedSensitiveInformationMapAdapter.fromJson( + this@decodeToEncryptedSensitiveInformation.decodeToString(throwOnInvalidSequence = true), + ) } } private suspend fun ByteArray.decodeToSensitiveInformation(): UserWalletSensitiveInformation? { return withContext(Dispatchers.Default) { try { - this@decodeToSensitiveInformation - .decodeToString(throwOnInvalidSequence = true) - .let(sensitiveInformationAdapter::fromJson) + sensitiveInformationAdapter.fromJson( + this@decodeToSensitiveInformation.decodeToString(throwOnInvalidSequence = true), + ) } catch (e: CharacterCodingException) { Timber.e(e, "Unable to decode sensitive information") @@ -132,16 +132,14 @@ internal class DefaultUserWalletsSensitiveInformationRepository( private suspend fun UserWalletSensitiveInformation.encode(): ByteArray { return withContext(Dispatchers.Default) { - this@encode - .let(sensitiveInformationAdapter::toJson) + sensitiveInformationAdapter.toJson(this@encode) .encodeToByteArray(throwOnInvalidSequence = true) } } private suspend fun Map.encode(): ByteArray { return withContext(Dispatchers.Default) { - this@encode - .let(encryptedSensitiveInformationMapAdapter::toJson) + encryptedSensitiveInformationMapAdapter.toJson(this@encode) .encodeToByteArray(throwOnInvalidSequence = true) } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt index ec627a4e34..ea08934377 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -54,14 +54,14 @@ internal fun UserWalletPublicInformation.toUserWallet(): UserWallet { walletId = walletId, hotWalletId = hotWalletId, wallets = null, - backedUp = backedUp!!, + backedUp = requireNotNull(backedUp), ) } else { UserWallet.Cold( name = name, walletId = walletId, cardsInWallet = cardsInWallet, - scanResponse = scanResponse!!, + scanResponse = requireNotNull(scanResponse), isMultiCurrency = isMultiCurrency, hasBackupError = hasBackupError, ) @@ -78,7 +78,7 @@ internal fun UserWallet.updateWith(sensitiveInformation: UserWalletSensitiveInfo copy( scanResponse = scanResponse.copy( card = scanResponse.card.copy( - wallets = sensitiveInformation.wallets!!, + wallets = requireNotNull(sensitiveInformation.wallets), ), visaCardActivationStatus = sensitiveInformation.visaCardActivationStatus, ), diff --git a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt index d074006981..931d258b38 100644 --- a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt +++ b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt @@ -87,9 +87,9 @@ internal class VisaCardScanHandler @Inject constructor( cardId = card.cardId, // This is the wallet public key, not the address and it's alright, as the API expects it in this format cardWalletAddress = wallet.publicKey.toHexString(), - ).getOrElse { + ).getOrElse { error -> Timber.i("Failed to get Access token for Wallet public key authorization") - return CompletionResult.Failure(it.tangemError) + return CompletionResult.Failure(error.tangemError) } val signChallengeResult = signChallengeWithWallet( @@ -123,16 +123,16 @@ internal class VisaCardScanHandler @Inject constructor( signedChallenge: VisaAuthSignedChallenge, ): CompletionResult { val authorizationTokensResponse = visaAuthRemoteDataSource.getAccessTokens(signedChallenge = signedChallenge) - .getOrElse { + .getOrElse { error -> Timber.i("Failed to get Access token for Wallet public key authorization.") return if ( - it is VisaApiError.ProductInstanceIsNotActivated || - it is VisaApiError.ProductInstanceNotFoundActivationRequired + error is VisaApiError.ProductInstanceIsNotActivated || + error is VisaApiError.ProductInstanceNotFoundActivationRequired ) { Timber.i("Proceeding with card authorization.") handleCardAuthorization(cardWalletAddress = cardWalletAddress) } else { - CompletionResult.Failure(it.tangemError) + CompletionResult.Failure(error.tangemError) } } @@ -152,9 +152,9 @@ internal class VisaCardScanHandler @Inject constructor( val challengeResponse = visaAuthRemoteDataSource.getCardAuthChallenge( cardId = card.cardId, cardPublicKey = card.cardPublicKey.toHexString(), - ).getOrElse { - Timber.e("Failed to get challenge for Card authorization. Plain error: ${it.errorCode}") - return CompletionResult.Failure(it.tangemError) + ).getOrElse { error -> + Timber.e("Failed to get challenge for Card authorization. Plain error: ${error.errorCode}") + return CompletionResult.Failure(error.tangemError) } Timber.i("Received challenge to sign: ${challengeResponse.challenge}") @@ -179,9 +179,9 @@ internal class VisaCardScanHandler @Inject constructor( signedChallenge = attestCardKeyResponse.cardSignature.toHexString(), salt = attestCardKeyResponse.salt.toHexString(), ), - ).getOrElse { - Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.errorCode}") - return CompletionResult.Failure(it.tangemError) + ).getOrElse { error -> + Timber.e("Failed to sign challenge with Card public key. Plain error: ${error.errorCode}") + return CompletionResult.Failure(error.tangemError) } visaAuthTokenStorage.store( @@ -189,9 +189,9 @@ internal class VisaCardScanHandler @Inject constructor( tokens = authorizationTokensResponse, ) - val activationRemoteState = visaActivationRepository.getActivationRemoteState().getOrElse { - Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.errorCode}") - return CompletionResult.Failure(it.tangemError) + val activationRemoteState = visaActivationRepository.getActivationRemoteState().getOrElse { error -> + Timber.e("Failed to sign challenge with Card public key. Plain error: ${error.errorCode}") + return CompletionResult.Failure(error.tangemError) } val error = when (activationRemoteState) { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index d398da42c9..0c647de6c4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -6,6 +6,7 @@ import com.tangem.domain.models.scan.ScanResponse import kotlinx.coroutines.CoroutineScope import org.rekotlin.Action +@Suppress("BooleanPropertyNaming") sealed class DetailsAction : Action { data class PrepareScreen( @@ -32,7 +33,7 @@ sealed class DetailsAction : Action { data object EnrollBiometrics : AppSettings() data class BiometricsStatusChanged( - val needEnrollBiometrics: Boolean, + val isEnrollBiometricsNeeded: Boolean, ) : AppSettings() data class ChangeAppThemeMode( @@ -40,7 +41,7 @@ sealed class DetailsAction : Action { ) : AppSettings() data class ChangeBalanceHiding( - val hideBalance: Boolean, + val shouldHideBalance: Boolean, ) : AppSettings() data class ChangeAppCurrency( diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 2ebe0494ab..d42bb74f37 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -86,7 +86,7 @@ class DetailsMiddleware { changeAppThemeMode(action.appThemeMode) } is DetailsAction.AppSettings.ChangeBalanceHiding -> { - changeBalanceHiding(action.hideBalance) + changeBalanceHiding(action.shouldHideBalance) } is DetailsAction.AppSettings.ChangeAppCurrency -> { store.dispatch(GlobalAction.ChangeAppCurrency(action.currency)) @@ -153,9 +153,9 @@ class DetailsMiddleware { private suspend fun setBiometricLockForAllWallets() { val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository) val userWallets = userWalletsListRepository.userWalletsSync() - userWallets.forEach { + userWallets.forEach { wallet -> userWalletsListRepository.setLock( - userWalletId = it.walletId, + userWalletId = wallet.walletId, lockMethod = LockMethod.Biometric, changeUnsecured = false, ) @@ -174,11 +174,11 @@ class DetailsMiddleware { deleteSavedAccessCodes() val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository) val tangemHotSdk = store.inject(DaggerGraphState::tangemHotSdk) - userWalletsListRepository.userWalletsSync().forEach { - if (it is UserWallet.Hot) { + userWalletsListRepository.userWalletsSync().forEach { wallet -> + if (wallet is UserWallet.Hot) { userWalletsListRepository.saveWithoutLock( - userWallet = it.copy( - hotWalletId = tangemHotSdk.removeBiometryAuthIfPresented(it.hotWalletId), + userWallet = wallet.copy( + hotWalletId = tangemHotSdk.removeBiometryAuthIfPresented(wallet.hotWalletId), ), ) } @@ -188,10 +188,10 @@ class DetailsMiddleware { private fun observeBiometricsStatusChanges(scope: CoroutineScope) { val needEnrollBiometricsFlow = flow { do { - val needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() + val isEnrollBiometricsNeeded = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() - if (needEnrollBiometrics != null) { - emit(needEnrollBiometrics) + if (isEnrollBiometricsNeeded != null) { + emit(isEnrollBiometricsNeeded) } delay(timeMillis = 200) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index e783c18e37..5b219a6c22 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -84,7 +84,7 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail ) is DetailsAction.AppSettings.BiometricsStatusChanged -> state.copy( appSettingsState = state.appSettingsState.copy( - needEnrollBiometrics = action.needEnrollBiometrics, + needEnrollBiometrics = action.isEnrollBiometricsNeeded, ), ) is DetailsAction.AppSettings.ChangeAppThemeMode -> state.copy( @@ -99,7 +99,7 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail ) is DetailsAction.AppSettings.ChangeBalanceHiding -> state.copy( appSettingsState = state.appSettingsState.copy( - isHidingEnabled = action.hideBalance, + isHidingEnabled = action.shouldHideBalance, ), ) // state should be copied to avoid concurrent modifications from different sources diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index 980cacb286..f7e7eaa6dc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -11,6 +11,7 @@ data class DetailsState( val appSettingsState: AppSettingsState = AppSettingsState(), ) : StateType +@Suppress("BooleanPropertyNaming") data class AppSettingsState( @Deprecated("Delete after hot wallet release") val saveWallets: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt index c4cfcfd7ff..2ccdbdd1f0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt @@ -341,16 +341,18 @@ private class AppCurrencySelectorStateProvider : CollectionPreviewParameterProvi .mapIndexed { index, s -> Currency(index.toString(), s) } .toPersistentList() - AppCurrencySelectorState.Loading(onBackClick = {}).let(::add) - AppCurrencySelectorState.Default( + this + AppCurrencySelectorState.Loading(onBackClick = {}) + + this + AppCurrencySelectorState.Default( selectedId = "0", items = items, scrollToSelected = consumedEvent(), onCurrencyClick = {}, onBackClick = {}, onTopBarActionClick = {}, - ).let(::add) - AppCurrencySelectorState.Search( + ) + + this + AppCurrencySelectorState.Search( selectedId = "0", items = items, scrollToSelected = consumedEvent(), @@ -358,7 +360,7 @@ private class AppCurrencySelectorStateProvider : CollectionPreviewParameterProvi onBackClick = {}, onSearchInputChange = {}, onTopBarActionClick = {}, - ).let(::add) + ) }, ) // endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt index f3d0b721c9..b721c113d5 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -15,8 +15,8 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item import com.tangem.tap.features.details.ui.appsettings.components.* @@ -27,16 +27,16 @@ import kotlinx.collections.immutable.persistentListOf @Composable internal fun AppSettingsScreen(state: AppSettingsScreenState, onBackClick: () -> Unit, modifier: Modifier = Modifier) { SettingsScreensScaffold( + onBackClick = onBackClick, modifier = modifier, + titleRes = R.string.app_settings_title, + addBottomInsets = false, content = { when (state) { is AppSettingsScreenState.Content -> AppSettings(state = state) is AppSettingsScreenState.Loading -> Unit } }, - titleRes = R.string.app_settings_title, - onBackClick = onBackClick, - addBottomInsets = false, ) } @@ -103,10 +103,10 @@ private class AppSettingsScreenStateProvider : CollectionPreviewParameterProvide itemsFactory.createSelectThemeModeButton(AppThemeMode.DEFAULT, {}), ) - AppSettingsScreenState.Content( + this + AppSettingsScreenState.Content( items = items, dialog = null, - ).let(::add) + ) }, ) // endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt index 29290619e6..327cb900a1 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt @@ -47,8 +47,8 @@ private class AlertDialogProvider : CollectionPreviewParameterProvider( collection = buildList { val itemsFactory = AppSettingsItemsFactory() - itemsFactory.createEnrollBiometricsCard( - onClick = { /* no-op */ }, - ).let(::add) + this + itemsFactory.createEnrollBiometricsCard(onClick = { /* no-op */ }) }, ) // endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt index 9ad210cbef..a31eb1484d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt @@ -17,8 +17,8 @@ import com.tangem.core.ui.components.SpacerH4 import com.tangem.core.ui.components.SpacerW32 import com.tangem.core.ui.components.TangemSwitch import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item @@ -85,26 +85,29 @@ private class SwitchItemProvider : CollectionPreviewParameterProvider { - val items = buildList { - if (state.needEnrollBiometrics) { - itemsFactory.createEnrollBiometricsCard( - onClick = ::enrollBiometrics, - ).let(::add) - } + val items = buildList { + addIf( + condition = state.needEnrollBiometrics, + element = itemsFactory.createEnrollBiometricsCard(onClick = ::enrollBiometrics), + ) - itemsFactory.createSelectAppCurrencyButton( + this + itemsFactory.createSelectAppCurrencyButton( currentAppCurrencyName = state.selectedAppCurrency.name, onClick = ::showAppCurrencySelector, - ).let(::add) + ) if (hotWalletFeatureToggles.isHotWalletEnabled) { val canUseBiometrics = !state.needEnrollBiometrics && !state.isInProgress - itemsFactory.createUseBiometricsSwitch( + this + itemsFactory.createUseBiometricsSwitch( isChecked = state.useBiometricAuthentication, isEnabled = canUseBiometrics, onCheckedChange = ::onBiometricAuthenticationToggled, - ).let(::add) + ) - itemsFactory.createRequireAccessCodeSwitch( + this + itemsFactory.createRequireAccessCodeSwitch( isChecked = state.requireAccessCode, isEnabled = canUseBiometrics && state.useBiometricAuthentication, onCheckedChange = ::onRequireAccessCodeToggled, - ).let(::add) + ) } else { if (state.isBiometricsAvailable) { val canUseBiometrics = !state.needEnrollBiometrics && !state.isInProgress - itemsFactory.createSaveWalletsSwitch( + this + itemsFactory.createSaveWalletsSwitch( isChecked = state.saveWallets, isEnabled = canUseBiometrics, onCheckedChange = ::onSaveWalletsToggled, - ).let(::add) + ) - itemsFactory.createSaveAccessCodeSwitch( + this + itemsFactory.createSaveAccessCodeSwitch( isChecked = state.saveAccessCodes, isEnabled = canUseBiometrics, onCheckedChange = ::onSaveAccessCodesToggled, - ).let(::add) + ) } } - itemsFactory.createFlipToHideBalanceSwitch( + this + itemsFactory.createFlipToHideBalanceSwitch( isChecked = state.isHidingEnabled, isEnabled = true, onCheckedChange = ::onFlipToHideBalanceToggled, - ).let(::add) + ) - itemsFactory.createSelectThemeModeButton( + this + itemsFactory.createSelectThemeModeButton( currentThemeMode = state.selectedThemeMode, onClick = { showThemeModeSelector(state.selectedThemeMode) }, - ).let(::add) + ) } return items.toImmutableList() @@ -280,7 +280,7 @@ internal class AppSettingsModel @Inject constructor( val param = AnalyticsParam.OnOffState(enable) analyticsEventHandler.send(Settings.AppSettings.HideBalanceChanged(param)) - store.dispatch(DetailsAction.AppSettings.ChangeBalanceHiding(hideBalance = enable)) + store.dispatch(DetailsAction.AppSettings.ChangeBalanceHiding(shouldHideBalance = enable)) } private fun dismissDialog() { @@ -290,10 +290,10 @@ internal class AppSettingsModel @Inject constructor( private fun bootstrapAppCurrencyUpdates() { appCurrencyRepository .getSelectedAppCurrency() - .onEach { - if (it.code == store.state.globalState.appCurrency.code) return@onEach + .onEach { appCurrency -> + if (appCurrency.code == store.state.globalState.appCurrency.code) return@onEach - store.dispatchWithMain(DetailsAction.AppSettings.ChangeAppCurrency(it)) + store.dispatchWithMain(DetailsAction.AppSettings.ChangeAppCurrency(appCurrency)) } .launchIn(scope) .saveIn(appCurrencyUpdatesJobHolder) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index eb76dc0b7a..a81c2bdbd1 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -26,19 +26,19 @@ import com.tangem.wallet.R @Composable internal fun CardSettingsScreen(state: CardSettingsScreenState, modifier: Modifier = Modifier) { - val needReadCard = state.cardDetails == null + val isCardReadingNeeded = state.cardDetails == null SettingsScreensScaffold( + onBackClick = state.onBackClick, modifier = modifier, + titleRes = R.string.card_settings_title, content = { - if (needReadCard) { + if (isCardReadingNeeded) { CardSettingsReadCard(state.onScanCardClick) } else { CardSettings(state = state) } }, - titleRes = R.string.card_settings_title, - onBackClick = state.onBackClick, ) } @@ -123,8 +123,8 @@ private fun CardSettings(state: CardSettingsScreenState) { .fillMaxWidth() .testTag(DeviceSettingsScreenTestTags.LAZY_LIST), ) { - items(state.cardDetails) { - val paddingBottom = when (it) { + items(state.cardDetails) { cardInfo -> + val paddingBottom = when (cardInfo) { is CardInfo.CardId, is CardInfo.Issuer -> TangemTheme.dimens.spacing12 is CardInfo.SignedHashes -> TangemTheme.dimens.spacing14 is CardInfo.SecurityMode -> TangemTheme.dimens.spacing16 @@ -132,7 +132,7 @@ private fun CardSettings(state: CardSettingsScreenState) { is CardInfo.AccessCodeRecovery -> TangemTheme.dimens.spacing16 is CardInfo.ResetToFactorySettings -> TangemTheme.dimens.spacing28 } - val paddingTop = when (it) { + val paddingTop = when (cardInfo) { is CardInfo.CardId -> TangemTheme.dimens.spacing0 is CardInfo.Issuer -> TangemTheme.dimens.spacing12 is CardInfo.SignedHashes -> TangemTheme.dimens.spacing12 @@ -145,8 +145,8 @@ private fun CardSettings(state: CardSettingsScreenState) { modifier = Modifier .fillMaxWidth() .clickable( - enabled = it.clickable, - onClick = { state.onElementClick(it) }, + enabled = cardInfo.isClickable, + onClick = { state.onElementClick(cardInfo) }, ) .padding( start = TangemTheme.dimens.spacing20, @@ -155,25 +155,25 @@ private fun CardSettings(state: CardSettingsScreenState) { top = paddingTop, ), ) { - val titleColor = if (it.clickable) { + val titleColor = if (cardInfo.isClickable) { TangemTheme.colors.text.primary1 } else { TangemTheme.colors.text.tertiary } - val subtitleColor = if (it.clickable) { + val subtitleColor = if (cardInfo.isClickable) { TangemTheme.colors.text.secondary } else { TangemTheme.colors.text.tertiary } Text( - text = it.titleRes.resolveReference(), + text = cardInfo.titleRes.resolveReference(), color = titleColor, style = TangemTheme.typography.subtitle1, modifier = Modifier.testTag(DeviceSettingsScreenTestTags.ITEM_TITLE), ) Spacer(modifier = Modifier.size(TangemTheme.dimens.size4)) Text( - text = it.subtitle.resolveReference(), + text = cardInfo.subtitle.resolveReference(), color = subtitleColor, style = TangemTheme.typography.body2, modifier = Modifier.testTag(DeviceSettingsScreenTestTags.ITEM_SUBTITLE), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt index cbf005de4c..5f4415c577 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt @@ -18,7 +18,7 @@ internal data class CardSettingsScreenState( internal sealed class CardInfo( val titleRes: TextReference, val subtitle: TextReference, - val clickable: Boolean = false, + val isClickable: Boolean = false, ) { class CardId(subtitle: String) : CardInfo( titleRes = TextReference.Res(R.string.details_row_title_cid), @@ -38,29 +38,29 @@ internal sealed class CardInfo( class SecurityMode(securityOption: SecurityOption, clickable: Boolean) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_security_mode), subtitle = TextReference.Res(securityOption.toTitleRes()), - clickable = clickable, + isClickable = clickable, ) data object ChangeAccessCode : CardInfo( titleRes = TextReference.Res(R.string.card_settings_change_access_code), subtitle = TextReference.Res(R.string.card_settings_change_access_code_footer), - clickable = true, + isClickable = true, ) - class AccessCodeRecovery(val enabled: Boolean) : CardInfo( + class AccessCodeRecovery(val isEnabled: Boolean) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_access_code_recovery_title), - subtitle = if (enabled) { + subtitle = if (isEnabled) { TextReference.Res(R.string.common_enabled) } else { TextReference.Res(R.string.common_disabled) }, - clickable = true, + isClickable = true, ) class ResetToFactorySettings(description: TextReference) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_reset_card_to_factory), subtitle = description, - clickable = true, + isClickable = true, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt index 8315639120..c40418cc3d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt @@ -16,8 +16,8 @@ import com.tangem.wallet.R @Composable fun AccessCodeRecoveryScreen(state: AccessCodeRecoveryScreenState, onBackClick: () -> Unit) { SettingsScreensScaffold( - content = { AccessCodeRecoveryOptions(state = state) }, onBackClick = onBackClick, + content = { AccessCodeRecoveryOptions(state = state) }, ) } @@ -38,13 +38,13 @@ fun AccessCodeRecoveryOptions(state: AccessCodeRecoveryScreenState) { DetailsRadioButtonElement( title = stringResourceSafe(id = R.string.common_enabled), subtitle = stringResourceSafe(id = R.string.card_settings_access_code_recovery_enabled_description), - selected = state.enabledSelection, + isSelected = state.isEnabledSelection, onClick = { state.onOptionClick(true) }, ) DetailsRadioButtonElement( title = stringResourceSafe(id = R.string.common_disabled), subtitle = stringResourceSafe(id = R.string.card_settings_access_code_recovery_disabled_description), - selected = !state.enabledSelection, + isSelected = !state.isEnabledSelection, onClick = { state.onOptionClick(false) }, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreenState.kt index b4b6c68535..2d2301ab70 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreenState.kt @@ -1,15 +1,15 @@ package com.tangem.tap.features.details.ui.cardsettings.coderecovery /** - * @property enabledOnCard Indicates whether access code recovery is enabled on the card - * @property enabledSelection Represents the currently selected option in the app (not yet saved on the card) + * @property isEnabledOnCard Indicates whether access code recovery is enabled on the card + * @property isEnabledSelection Represents the currently selected option in the app (not yet saved on the card) * @property isSaveChangesEnabled Determines if the user is allowed to save their selection to the card * @property onSaveChangesClick Callback function called when the user wants to apply the selected option * @property onOptionClick Callback function called when the user selects an option * */ data class AccessCodeRecoveryScreenState( - val enabledOnCard: Boolean, - val enabledSelection: Boolean, + val isEnabledOnCard: Boolean, + val isEnabledSelection: Boolean, val isSaveChangesEnabled: Boolean, val onSaveChangesClick: () -> Unit, val onOptionClick: (Boolean) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt index 298d2ac7d7..ac083db4c6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt @@ -43,8 +43,8 @@ internal class AccessCodeRecoveryModel @Inject constructor( ) return AccessCodeRecoveryScreenState( - enabledOnCard = isEnabled, - enabledSelection = isEnabled, + isEnabledOnCard = isEnabled, + isEnabledSelection = isEnabled, isSaveChangesEnabled = false, onSaveChangesClick = ::saveChanges, onOptionClick = ::selectOption, @@ -52,7 +52,7 @@ internal class AccessCodeRecoveryModel @Inject constructor( } private fun saveChanges() = modelScope.launch { - val isEnabled = screenState.value.enabledSelection + val isEnabled = screenState.value.isEnabledSelection tangemSdkManager .setAccessCodeRecoveryEnabled(scannedScanResponse.card.cardId, isEnabled) @@ -78,10 +78,10 @@ internal class AccessCodeRecoveryModel @Inject constructor( } private fun selectOption(isEnabled: Boolean) { - screenState.update { - it.copy( - enabledSelection = isEnabled, - isSaveChangesEnabled = isEnabled != it.enabledOnCard, + screenState.update { state -> + state.copy( + isEnabledSelection = isEnabled, + isSaveChangesEnabled = isEnabled != state.isEnabledOnCard, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/domain/CardSettingsInteractor.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/domain/CardSettingsInteractor.kt index 73bbf10991..09eb4199ef 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/domain/CardSettingsInteractor.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/domain/CardSettingsInteractor.kt @@ -23,9 +23,9 @@ internal class CardSettingsInteractor @Inject constructor() { } fun update(transform: (ScanResponse) -> ScanResponse) { - _scannedScanResponse.update { - requireNotNull(it) - transform(it) + _scannedScanResponse.update { scanResponse -> + requireNotNull(scanResponse) + transform(scanResponse) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt index 1d5b58407d..08aa5ef0b0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt @@ -12,9 +12,9 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.card.CardTypesResolver import com.tangem.domain.card.ScanCardProcessor -import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.card.common.util.getBackupCardsCount +import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.wallet.requireColdWallet @@ -34,6 +34,7 @@ import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsIntera import com.tangem.tap.features.details.ui.common.utils.* import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.extensions.addIf import com.tangem.wallet.R import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -57,7 +58,7 @@ internal class CardSettingsModel @Inject constructor( private val params = paramsContainer.require() - private var previousBiometricsRequestPolicy: Boolean = false + private var isBiometricsRequestPolicyPrevious: Boolean = false private val userWalletId = params.userWalletId @@ -77,20 +78,19 @@ internal class CardSettingsModel @Inject constructor( // Reset card scanned data cardSettingsInteractor.clear() // Restore the previous value of access code request policy - cardSdkConfigRepository.isBiometricsRequestPolicy = previousBiometricsRequestPolicy + cardSdkConfigRepository.isBiometricsRequestPolicy = isBiometricsRequestPolicyPrevious } private fun updateAccessCodeRequestPolicy() { runBlocking { // !!!IMPORTANT!!!: Do not forget to restore the previous value in onCleared() method - previousBiometricsRequestPolicy = cardSdkConfigRepository.isBiometricsRequestPolicy + isBiometricsRequestPolicyPrevious = cardSdkConfigRepository.isBiometricsRequestPolicy val userWallet = getUserWalletUseCase(userWalletId) .getOrElse { error("User wallet $userWalletId not found") } .requireColdWallet() - cardSdkConfigRepository.isBiometricsRequestPolicy = - userWallet.scanResponse.card.isAccessCodeSet && + cardSdkConfigRepository.isBiometricsRequestPolicy = userWallet.scanResponse.card.isAccessCodeSet && settingsRepository.shouldSaveAccessCodes() } } @@ -135,34 +135,39 @@ internal class CardSettingsModel @Inject constructor( ) val isResetCardAllowed = isResetToFactoryAllowedByCard(card, cardTypesResolver) - val cardDetails = buildList { - CardInfo.CardId(cardId).let(::add) - CardInfo.Issuer(card.issuer.name).let(::add) + val cardDetails: List = buildList { + add(CardInfo.CardId(cardId)) + add(CardInfo.Issuer(card.issuer.name)) - if (!cardTypesResolver.isTangemTwins()) { - CardInfo.SignedHashes(card.signedHashesCount().toString()).let(::add) - } + addIf( + condition = !cardTypesResolver.isTangemTwins(), + element = CardInfo.SignedHashes(card.signedHashesCount().toString()), + ) - CardInfo.SecurityMode( - currentSecurityOption, - clickable = allowedSecurityOptions.size > 1, - ).let(::add) + add( + CardInfo.SecurityMode( + securityOption = currentSecurityOption, + clickable = allowedSecurityOptions.size > 1, + ), + ) - if (card.backupStatus?.isActive == true && card.isAccessCodeSet) { - CardInfo.ChangeAccessCode.let(::add) - } + addIf( + condition = card.backupStatus?.isActive == true && card.isAccessCodeSet, + element = CardInfo.ChangeAccessCode, + ) - if (isAccessCodeRecoveryAllowed(cardTypesResolver)) { - CardInfo.AccessCodeRecovery(isAccessCodeRecoveryEnabled(cardTypesResolver, card)).let(::add) - } + addIf( + condition = isAccessCodeRecoveryAllowed(cardTypesResolver), + element = CardInfo.AccessCodeRecovery(isAccessCodeRecoveryEnabled(cardTypesResolver, card)), + ) - if (isResetCardAllowed) { + addIf(isResetCardAllowed) { CardInfo.ResetToFactorySettings( description = getResetToFactoryDescription( isActiveBackupStatus = card.backupStatus?.isActive == true, typesResolver = cardTypesResolver, ), - ).let(::add) + ) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt index 98c792c2ef..19afbf0689 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -19,11 +19,11 @@ import com.tangem.wallet.R @Composable internal fun SettingsScreensScaffold( onBackClick: () -> Unit, - content: @Composable () -> Unit, modifier: Modifier = Modifier, @StringRes titleRes: Int? = null, - snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, addBottomInsets: Boolean = true, + snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, + content: @Composable () -> Unit, fab: @Composable () -> Unit = {}, ) { val backgroundColor = TangemTheme.colors.background.secondary @@ -129,18 +129,18 @@ internal fun DetailsMainButton( } @Composable -internal fun DetailsRadioButtonElement(title: String, subtitle: String, selected: Boolean, onClick: () -> Unit) { +internal fun DetailsRadioButtonElement(title: String, subtitle: String, isSelected: Boolean, onClick: () -> Unit) { Row( modifier = Modifier .fillMaxWidth() .selectable( - selected = selected, + selected = isSelected, onClick = { onClick() }, ) .padding(start = 20.dp, end = 20.dp, top = 16.dp, bottom = 16.dp), ) { RadioButton( - selected = selected, + selected = isSelected, onClick = null, modifier = Modifier.padding(end = 20.dp), colors = RadioButtonDefaults.colors( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/AccessCodeRecovery.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/AccessCodeRecovery.kt index 73af54606b..6d6a21df15 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/AccessCodeRecovery.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/AccessCodeRecovery.kt @@ -7,7 +7,7 @@ internal fun isAccessCodeRecoveryAllowed(typeResolver: CardTypesResolver): Boole internal fun isAccessCodeRecoveryEnabled(typeResolver: CardTypesResolver, card: CardDTO): Boolean = if (typeResolver.isWallet2()) { - card.userSettings?.isUserCodeRecoveryAllowed ?: false + card.userSettings?.isUserCodeRecoveryAllowed == true } else { false } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index e81602a4d2..b6557e7f4c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -27,11 +27,11 @@ import com.tangem.tap.features.details.ui.resetcard.ResetCardScreenState.Dialog @Composable internal fun ResetCardScreen(state: ResetCardScreenState, onBackClick: () -> Unit, modifier: Modifier = Modifier) { SettingsScreensScaffold( + onBackClick = onBackClick, modifier = modifier, content = { ResetCardView(state = state) }, - onBackClick = onBackClick, ) when (val dialog = state.dialog) { @@ -65,7 +65,7 @@ private fun ResetCardView(state: ResetCardScreenState) { Conditions(state) DynamicSpacer(scrollState = scrollState) SpacerH16() - ResetButton(enabled = state.resetButtonEnabled, onResetButtonClick = state.onResetButtonClick) + ResetButton(enabled = state.isResetButtonEnabled, onResetButtonClick = state.onResetButtonClick) SpacerH16() } } @@ -113,18 +113,18 @@ private fun Description(text: TextReference) { @Composable private fun Conditions(state: ResetCardScreenState) { - state.warningsToShow.forEach { - when (it) { + state.warningsToShow.forEach { warning -> + when (warning) { ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS -> { ConditionCheckBox( - checkedState = state.acceptCondition1Checked, + checkedState = state.isAcceptCondition1Checked, onCheckedChange = state.onAcceptCondition1ToggleClick, description = TextReference.Res(R.string.reset_card_to_factory_condition_1), ) } ResetCardScreenState.WarningsToReset.LOST_PASSWORD_RESTORE -> { ConditionCheckBox( - checkedState = state.acceptCondition2Checked, + checkedState = state.isAcceptCondition2Checked, onCheckedChange = state.onAcceptCondition2ToggleClick, description = TextReference.Res(R.string.reset_card_to_factory_condition_2), ) @@ -238,12 +238,12 @@ private fun ResetCardScreenSample(modifier: Modifier = Modifier) { ) { ResetCardScreen( state = ResetCardScreenState( - resetButtonEnabled = true, - showResetPasswordButton = false, + isResetButtonEnabled = true, + isResetPasswordButtonShown = false, warningsToShow = listOf(ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS), descriptionText = TextReference.Res(R.string.reset_card_with_backup_to_factory_message), - acceptCondition1Checked = false, - acceptCondition2Checked = false, + isAcceptCondition1Checked = false, + isAcceptCondition2Checked = false, onAcceptCondition1ToggleClick = {}, onAcceptCondition2ToggleClick = {}, onResetButtonClick = {}, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt index 27dacf387f..32eb289aec 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt @@ -5,12 +5,12 @@ import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.wallet.R internal data class ResetCardScreenState( - val resetButtonEnabled: Boolean, + val isResetButtonEnabled: Boolean, val descriptionText: TextReference, val warningsToShow: List, - val showResetPasswordButton: Boolean, - val acceptCondition1Checked: Boolean, - val acceptCondition2Checked: Boolean, + val isResetPasswordButtonShown: Boolean, + val isAcceptCondition1Checked: Boolean, + val isAcceptCondition2Checked: Boolean, val onAcceptCondition1ToggleClick: (Boolean) -> Unit, val onAcceptCondition2ToggleClick: (Boolean) -> Unit, val onResetButtonClick: () -> Unit, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt index 6cab06fac6..eaa4ce0a9f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt @@ -97,15 +97,15 @@ internal class ResetCardModel @Inject constructor( } return ResetCardScreenState( - resetButtonEnabled = false, + isResetButtonEnabled = false, descriptionText = getResetToFactoryDescription( isActiveBackupStatus = isActiveBackupPrimaryCard, typesResolver = currentCardTypesResolver, ), warningsToShow = warningsToShow, - showResetPasswordButton = shouldShowResetPasswordButton, - acceptCondition1Checked = false, - acceptCondition2Checked = false, + isResetPasswordButtonShown = shouldShowResetPasswordButton, + isAcceptCondition1Checked = false, + isAcceptCondition2Checked = false, onAcceptCondition1ToggleClick = ::toggleFirstCondition, onAcceptCondition2ToggleClick = ::toggleSecondCondition, onResetButtonClick = { showDialog(ResetCardDialog.StartResetDialog) }, @@ -121,26 +121,26 @@ internal class ResetCardModel @Inject constructor( private fun toggleFirstCondition(isAccepted: Boolean) { screenState.update { prevState -> - val resetButtonEnabled = if (prevState.showResetPasswordButton) { - isAccepted && prevState.acceptCondition2Checked + val isResetButtonEnabled = if (prevState.isResetPasswordButtonShown) { + isAccepted && prevState.isAcceptCondition2Checked } else { isAccepted } prevState.copy( - acceptCondition1Checked = isAccepted, - resetButtonEnabled = resetButtonEnabled, + isAcceptCondition1Checked = isAccepted, + isResetButtonEnabled = isResetButtonEnabled, ) } } private fun toggleSecondCondition(isAccepted: Boolean) { screenState.update { prevState -> - val resetButtonEnabled = prevState.acceptCondition1Checked && isAccepted + val isResetButtonEnabled = prevState.isAcceptCondition1Checked && isAccepted prevState.copy( - acceptCondition2Checked = isAccepted, - resetButtonEnabled = resetButtonEnabled, + isAcceptCondition2Checked = isAccepted, + isResetButtonEnabled = isResetButtonEnabled, ) } } @@ -183,14 +183,14 @@ internal class ResetCardModel @Inject constructor( modelScope.launch { resetCardUseCase(cardId = primaryCardId, params = currentUserCodeParams).onRight { deleteSavedAccessCodesUseCase(cardId = primaryCardId) - val hasUserWallets = deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse { - Timber.e("Unable to delete user wallet: $it") + val hasUserWallets = deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse { error -> + Timber.e("Unable to delete user wallet: $error") return@launch } if (hasUserWallets) { - val newSelectedWallet = getSelectedWalletSyncUseCase().getOrElse { - error("Failed to get selected wallet: $it") + val newSelectedWallet = getSelectedWalletSyncUseCase().getOrElse { error -> + error("Failed to get selected wallet: $error") } store.onUserWalletSelected(newSelectedWallet) @@ -269,7 +269,7 @@ internal class ResetCardModel @Inject constructor( if (hotWalletFeatureToggles.isHotWalletEnabled) { store.dispatchNavigationAction { replaceAll(AppRoute.Home()) } } else { - val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }.isSuccess + val isLocked = runCatching { userWalletsListManager.asLockable()?.isLocked }.isSuccess if (isLocked && userWalletsListManager.hasUserWallets) { store.dispatchNavigationAction { popTo() } } else { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt index 3a86a4f67c..981cd1c76a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt @@ -22,10 +22,10 @@ internal fun SecurityModeScreen( modifier: Modifier = Modifier, ) { SettingsScreensScaffold( + onBackClick = onBackClick, modifier = modifier, content = { SecurityModeOptions(state = state) }, // titleRes = R.string.card_settings_security_mode, - onBackClick = onBackClick, ) } @@ -57,7 +57,7 @@ private fun SecurityModeOptions(state: SecurityModeScreenState) { @Composable private fun SecurityOption(option: SecurityOption, state: SecurityModeScreenState) { - val selected = option == state.selectedSecurityMode + val isSelected = option == state.selectedSecurityMode val title = option.toTitleRes() @@ -70,7 +70,7 @@ private fun SecurityOption(option: SecurityOption, state: SecurityModeScreenStat DetailsRadioButtonElement( title = stringResourceSafe(id = title), subtitle = stringResourceSafe(id = subtitle), - selected = selected, + isSelected = isSelected, onClick = { state.onNewModeSelected(option) }, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt index ee9cf3dd51..36fdf29787 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt @@ -77,9 +77,9 @@ internal class SecurityModeModel @Inject constructor( SecurityOption.AccessCode -> tangemSdkManager.setAccessCode(cardId) } - cardSettingsInteractor.update { - it.copy( - card = it.card.copy( + cardSettingsInteractor.update { scanResponse -> + scanResponse.copy( + card = scanResponse.card.copy( isAccessCodeSet = selectedOption == SecurityOption.AccessCode, isPasscodeSet = selectedOption == SecurityOption.PassCode, ), diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 1734b45276..1753406d30 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -150,7 +150,7 @@ internal class MainViewModel @Inject constructor( prepareSelectedWalletFeedback() // await while initial route stack is initialized - appRouterConfig.isInitialized.first { it } + appRouterConfig.initializedState.first { it } isSplashScreenShown = false } @@ -179,11 +179,9 @@ internal class MainViewModel @Inject constructor( private fun prepareSelectedWalletFeedback() { getSelectedWalletUseCase.invoke() .mapLeft { emptyFlow() } - .onRight { - it.distinctUntilChanged() - .onEach { userWallet -> - Analytics.setContext(userWallet) - } + .onRight { wallet -> + wallet.distinctUntilChanged() + .onEach { Analytics.setContext(it) } .flowOn(dispatchers.io) .launchIn(viewModelScope) } @@ -208,7 +206,7 @@ internal class MainViewModel @Inject constructor( return MoonPayService( apiKey = environmentConfig.moonPayApiKey, secretKey = environmentConfig.moonPayApiSecretKey, - logEnabled = LogConfig.network.moonPayService, + isLogEnabled = LogConfig.network.moonPayService, userWalletProvider = { getSelectedWalletUseCase.sync().getOrNull() }, ) } @@ -224,8 +222,8 @@ internal class MainViewModel @Inject constructor( .filter { it.isBalanceHidingNotificationEnabled && it.isBalanceHidden } - .onEach { - if (!it.isUpdateFromToast) { + .onEach { settings -> + if (!settings.isUpdateFromToast) { listenToFlipsUseCase.changeUpdateEnabled(false) val message = BottomSheetMessage.invoke( @@ -354,6 +352,7 @@ internal class MainViewModel @Inject constructor( listenToFlipsUseCase.changeUpdateEnabled(isUpdateEnabled = true) } + @Suppress("NullableToStringCall") private fun sendKeyboardIdentifierEvent() { viewModelScope.launch { val keyboardId = keyboardValidator.getKeyboardId() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 231ce5256e..ec4e9e12ab 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -34,11 +34,11 @@ object OnboardingHelper { } response.cardTypesResolver.isWallet2() || response.cardTypesResolver.isShibaWallet() -> { - val emptyWallets = response.card.wallets.isEmpty() - val activationInProgress = cardRepository.isActivationInProgress(cardId) + val areWalletsEmpty = response.card.wallets.isEmpty() + val isActivationInProgress = cardRepository.isActivationInProgress(cardId) val isNoBackup = response.card.backupStatus == CardDTO.BackupStatus.NoBackup && !DemoHelper.isDemoCard(response) - emptyWallets || activationInProgress || isNoBackup + areWalletsEmpty || isActivationInProgress || isNoBackup } response.card.wallets.isNotEmpty() -> cardRepository.isActivationInProgress(cardId) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 2aa7a3ef24..6cbc795c53 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -50,8 +50,8 @@ object TradeCryptoMiddleware { fiatCurrencyName = action.appCurrencyCode, walletAddress = networkAddress, isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive, - )?.let { - store.dispatchOpenUrl(it) + )?.let { url -> + store.dispatchOpenUrl(url) Analytics.send(Token.Withdraw.ScreenOpened()) } } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt index ae5afc8d33..f6e882e5fb 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt @@ -72,8 +72,8 @@ internal class WelcomeModel @Inject constructor( this.state.update { prevState -> prevState.copy( - showUnlockWithBiometricsProgress = state.isUnlockWithBiometricsInProgress, - showUnlockWithCardProgress = state.isUnlockWithCardInProgress, + isUnlockWithBiometricsProgressVisible = state.isUnlockWithBiometricsInProgress, + isUnlockWithCardProgressVisible = state.isUnlockWithCardInProgress, warning = warning, error = state.error ?.takeIf { !it.silent && warning == null } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt index 78fb0451e0..6d0e6b6183 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt @@ -13,7 +13,7 @@ internal sealed interface WelcomeAction : Action { object ProceedWithCard : WelcomeAction { object Success : WelcomeAction data class Error(val error: TangemError) : WelcomeAction - data class ChangeProgress(val showProgress: Boolean) : WelcomeAction + data class ChangeProgress(val isProgress: Boolean) : WelcomeAction } object CloseError : WelcomeAction diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index af60602c2b..be2955e491 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -141,13 +141,13 @@ internal class WelcomeMiddleware { onSuccess = { scanResponse -> scope.launch { onCardScanned(scanResponse) } }, - onFailure = { - when (it) { + onFailure = { error -> + when (error) { is TangemSdkError.ExceptionError -> { store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) } else -> { - store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(it)) + store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error)) } } }, diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt index 0d9645dea6..f324887d0a 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt @@ -25,7 +25,7 @@ internal object WelcomeReducer { isUnlockWithCardInProgress = false, ) is WelcomeAction.ProceedWithCard.ChangeProgress -> state.copy( - isUnlockWithCardInProgress = action.showProgress, + isUnlockWithCardInProgress = action.isProgress, ) is WelcomeAction.ProceedWithBiometrics.Success -> state.copy(isUnlockWithBiometricsInProgress = false) is WelcomeAction.ProceedWithCard.Success -> state.copy(isUnlockWithCardInProgress = false) diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt index 3ede2ae446..7c7e43f2b5 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt @@ -5,8 +5,8 @@ import com.tangem.tap.features.welcome.ui.model.WarningModel internal data class WelcomeScreenState( val onPopBack: () -> Unit = {}, - val showUnlockWithBiometricsProgress: Boolean = false, - val showUnlockWithCardProgress: Boolean = false, + val isUnlockWithBiometricsProgressVisible: Boolean = false, + val isUnlockWithCardProgressVisible: Boolean = false, val warning: WarningModel? = null, val error: TextReference? = null, val onUnlockClick: () -> Unit = {}, diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreen.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreen.kt index ee1a00bb48..de791e45da 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreen.kt @@ -39,8 +39,8 @@ internal fun WelcomeScreen(state: WelcomeScreenState, modifier: Modifier = Modif .systemBarsPadding(), ) { WelcomeScreenContent( - showUnlockProgress = state.showUnlockWithBiometricsProgress, - showScanCardProgress = state.showUnlockWithCardProgress, + showUnlockProgress = state.isUnlockWithBiometricsProgressVisible, + showScanCardProgress = state.isUnlockWithCardProgressVisible, onUnlockClick = state.onUnlockClick, onScanCardClick = state.onScanCardClick, ) @@ -57,8 +57,8 @@ internal fun WelcomeScreen(state: WelcomeScreenState, modifier: Modifier = Modif WarningDialog(warning) LaunchedEffect(errorMessage, state.onCloseError) { - errorMessage?.let { - snackbarHostState.showSnackbar(it) + errorMessage?.let { message -> + snackbarHostState.showSnackbar(message) state.onCloseError() } } @@ -82,8 +82,8 @@ private class WelcomeComponentPreviewProvider : PreviewParameterProvider { - return if (useNewListRepository) { + return if (shouldUseNewListRepository) { userWalletsListRepository.userWalletsSync() } else { userWalletsListManager.userWalletsSync @@ -47,7 +47,7 @@ internal class DefaultAuthProvider( } private suspend fun getSelectedWallet(): UserWallet? { - return if (useNewListRepository) { + return if (shouldUseNewListRepository) { userWalletsListRepository.selectedUserWalletSync() } else { userWalletsListManager.selectedUserWalletSync diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt index 15e74c5498..47188c465b 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt @@ -6,7 +6,7 @@ import java.util.concurrent.atomic.AtomicReference internal class DefaultExpressAuthProvider : ExpressAuthProvider { - private var uuid = AtomicReference(UUID.randomUUID()) + private val uuid = AtomicReference(UUID.randomUUID()) override fun getSessionId(): String { return uuid.get().toString() diff --git a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt index 95004c11d8..d29722ec81 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt @@ -32,7 +32,7 @@ internal class AuthModule { return DefaultAuthProvider( userWalletsListManager = userWalletsListManager, userWalletsListRepository = userWalletsListRepository, - useNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled, + shouldUseNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled, ) } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 6c4c20e0cc..5cf37e3a1c 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -54,7 +54,7 @@ internal class DefaultRampManager( sendUnavailabilityReason: ScenarioUnavailabilityReason?, ): Either { return either { - val sellSupportedByService = catch( + val isSellSupportedByService = catch( block = { val serviceCurrency = cryptoCurrencyConverter.convertBack(status.currency) @@ -78,7 +78,7 @@ internal class DefaultRampManager( } } - ensure(condition = sellSupportedByService) { + ensure(condition = isSellSupportedByService) { ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name) } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt index c8235a8f74..554a0b4265 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt @@ -34,6 +34,7 @@ data class MoonPayUserStatus( val stateCode: String, ) +@Suppress("BooleanPropertyNaming") @JsonClass(generateAdapter = true) data class MoonPayCurrencies( @Json(name = "type") val type: String, diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index 02bc4321ae..1f291b109b 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -26,7 +26,7 @@ import javax.crypto.spec.SecretKeySpec class MoonPayService( private val apiKey: String, private val secretKey: String, - private val logEnabled: Boolean, + private val isLogEnabled: Boolean, private val userWalletProvider: () -> UserWallet?, ) : ExchangeService { @@ -39,7 +39,7 @@ class MoonPayService( private val api: MoonPayApi by lazy { createRetrofitInstance( baseUrl = MoonPayApi.MOOONPAY_BASE_URL, - logEnabled = logEnabled, + logEnabled = isLogEnabled, ).create(MoonPayApi::class.java) } @@ -104,24 +104,35 @@ class MoonPayService( override fun availableForSell(currency: Currency): Boolean { val userWallet = userWalletProvider() ?: return false - val checkCardExchange = userWallet !is UserWallet.Cold || !userWallet.scanResponse.card.isStart2Coin + val isExchangeSupported = userWallet !is UserWallet.Cold || !userWallet.scanResponse.card.isStart2Coin - if (!checkCardExchange) return false + if (!isExchangeSupported) return false if (!isSellAllowed()) return false val availableForSell = status?.availableForSell ?: return false val supportedCurrency = currency.blockchain.moonPaySupportedCurrency ?: return false - return availableForSell.any { + return availableForSell.any { availableCurrency -> when (currency) { is Currency.Blockchain -> { - it.networkCode.equals(other = supportedCurrency.networkCode, ignoreCase = true) && - it.currencyCode.equals(other = supportedCurrency.currencyCode, ignoreCase = true) + availableCurrency.networkCode.equals( + other = supportedCurrency.networkCode, + ignoreCase = true, + ) && availableCurrency.currencyCode.equals( + other = supportedCurrency.currencyCode, + ignoreCase = true, + ) } is Currency.Token -> { - it.networkCode.equals(other = supportedCurrency.networkCode, ignoreCase = true) && - it.contractAddress.equals(other = currency.token.contractAddress, ignoreCase = true) + availableCurrency.networkCode.equals( + other = supportedCurrency.networkCode, + ignoreCase = true, + ) && + availableCurrency.contractAddress.equals( + other = currency.token.contractAddress, + ignoreCase = true, + ) } } } @@ -137,15 +148,18 @@ class MoonPayService( if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl() val supportedCurrency = blockchain.moonPaySupportedCurrency ?: return null - val moonpayCurrency = status?.availableForSell?.firstOrNull { + val moonpayCurrency = status?.availableForSell?.firstOrNull { availableCurrency -> when (cryptoCurrency) { is CryptoCurrency.Coin -> { - it.networkCode.equals(other = supportedCurrency.networkCode, ignoreCase = true) && - it.currencyCode.equals(other = supportedCurrency.currencyCode, ignoreCase = true) + availableCurrency.networkCode.equals(other = supportedCurrency.networkCode, ignoreCase = true) && + availableCurrency.currencyCode.equals(other = supportedCurrency.currencyCode, ignoreCase = true) } is CryptoCurrency.Token -> { - it.networkCode.equals(other = supportedCurrency.networkCode, ignoreCase = true) && - it.contractAddress.equals(other = cryptoCurrency.contractAddress, ignoreCase = true) + availableCurrency.networkCode.equals(other = supportedCurrency.networkCode, ignoreCase = true) && + availableCurrency.contractAddress.equals( + other = cryptoCurrency.contractAddress, + ignoreCase = true, + ) } } } ?: return null @@ -184,7 +198,7 @@ class MoonPayService( } private fun isSellAllowed(): Boolean { - return status?.responseUserStatus?.isSellAllowed ?: false + return status?.responseUserStatus?.isSellAllowed == true } private companion object { diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index d11526b9f4..f8350e77a9 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -48,13 +48,13 @@ class UserWalletManagerImpl( override suspend fun getNativeTokenBalance(networkId: String, derivationPath: String?): ProxyAmount? { val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } val walletManager = getActualWalletManager(blockchain, derivationPath) - return walletManager.wallet.amounts.firstNotNullOfOrNull { - it.takeIf { it.key is AmountType.Coin } - }?.value?.let { + return walletManager.wallet.amounts.firstNotNullOfOrNull { amountEntry -> + amountEntry.takeIf { amountEntry.key is AmountType.Coin } + }?.value?.let { amount -> ProxyAmount( - it.currencySymbol, - it.value ?: BigDecimal.ZERO, - it.decimals, + amount.currencySymbol, + amount.value ?: BigDecimal.ZERO, + amount.decimals, ) } } diff --git a/app/src/main/java/com/tangem/tap/routing/RootContent.kt b/app/src/main/java/com/tangem/tap/routing/RootContent.kt index 5c009849bc..66b66199d4 100644 --- a/app/src/main/java/com/tangem/tap/routing/RootContent.kt +++ b/app/src/main/java/com/tangem/tap/routing/RootContent.kt @@ -31,20 +31,21 @@ import com.tangem.core.ui.message.EventMessageEffect import com.tangem.core.ui.res.LocalRootBackgroundColor import com.tangem.core.ui.res.LocalSnackbarHostState import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.security.ProvideSecureFlagController import com.tangem.tap.routing.component.RoutingComponent import com.tangem.tap.routing.transitions.RoutingTransitionAnimationFactory -@Suppress("LongParameterList") +@Suppress("LongParameterList", "ReusedModifierInstance") @OptIn(ExperimentalDecomposeApi::class) @Composable internal fun RootContent( stack: Value>, backHandler: BackHandler, uiDependencies: UiDependencies, - wcContent: @Composable (modifier: Modifier) -> Unit, - hotAccessCodeContent: @Composable (modifier: Modifier) -> Unit, onBack: () -> Unit, modifier: Modifier = Modifier, + wcContent: @Composable (modifier: Modifier) -> Unit, + hotAccessCodeContent: @Composable (modifier: Modifier) -> Unit, ) { val context = LocalContext.current @@ -52,39 +53,41 @@ internal fun RootContent( activity = context as Activity, uiDependencies = uiDependencies, ) { - val snackbarHostState = LocalSnackbarHostState.current + ProvideSecureFlagController { + val snackbarHostState = LocalSnackbarHostState.current - Box(Modifier.background(LocalRootBackgroundColor.current.value)) { - Children( - modifier = modifier, - animation = childrenAnimation(backHandler = backHandler, onBack = onBack), - stack = stack, - ) { child -> - when (val instance = child.instance) { - is RoutingComponent.Child.Initial -> Unit - is RoutingComponent.Child.ComposableComponent -> { - instance.component.Content(Modifier.fillMaxSize()) - } - is RoutingComponent.Child.LegacyIntent -> { - // TODO: Remove and use it's own router: [REDACTED_JIRA] - LaunchedEffect(instance) { - startActivity(context, instance.intent, Bundle.EMPTY) + Box(Modifier.background(LocalRootBackgroundColor.current.value)) { + Children( + modifier = modifier, + animation = childrenAnimation(backHandler = backHandler, onBack = onBack), + stack = stack, + ) { child -> + when (val instance = child.instance) { + is RoutingComponent.Child.Initial -> Unit + is RoutingComponent.Child.ComposableComponent -> { + instance.component.Content(Modifier.fillMaxSize()) + } + is RoutingComponent.Child.LegacyIntent -> { + // TODO: Remove and use it's own router: [REDACTED_JIRA] + LaunchedEffect(instance) { + startActivity(context, instance.intent, Bundle.EMPTY) + } } } } + + wcContent(Modifier.fillMaxSize()) + + hotAccessCodeContent(Modifier.fillMaxSize()) + + TangemSnackbarHost( + modifier = Modifier + .align(Alignment.BottomCenter) + .navigationBarsPadding() + .padding(all = 16.dp), + hostState = snackbarHostState, + ) } - - wcContent(Modifier.fillMaxSize()) - - hotAccessCodeContent(Modifier.fillMaxSize()) - - TangemSnackbarHost( - modifier = Modifier - .align(Alignment.BottomCenter) - .navigationBarsPadding() - .padding(all = 16.dp), - hostState = snackbarHostState, - ) } EventMessageEffect() } diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index 8030c44f97..8be5a8edea 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -133,7 +133,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( AppRoute.Wallet } }.also { - appRouterConfig.isInitialized.value = true + appRouterConfig.initializedState.value = true checkForUnfinishedBackup() } } @@ -141,13 +141,13 @@ internal class DefaultRoutingComponent @AssistedInject constructor( @Composable override fun Content(modifier: Modifier) { RootContent( - modifier = modifier, stack = stack, + backHandler = backHandler, uiDependencies = uiDependencies, + onBack = router::pop, + modifier = modifier, wcContent = { wcRoutingComponent.Content(it) }, hotAccessCodeContent = { hotAccessCodeRequestComponent.Content(it) }, - backHandler = backHandler, - onBack = router::pop, ) } diff --git a/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt b/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt index 393ab3a7df..a5ee03adad 100644 --- a/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt +++ b/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt @@ -11,7 +11,7 @@ internal interface AppRouterConfig { var routerScope: CoroutineScope? var componentRouter: Router? var stack: List? - val isInitialized: MutableStateFlow + val initializedState: MutableStateFlow // TODO: Replace with UI message handler: [REDACTED_JIRA] var snackbarHandler: SnackbarHandler? diff --git a/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt b/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt index 776802e429..7831308228 100644 --- a/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt +++ b/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt @@ -11,5 +11,5 @@ internal class MutableAppRouterConfig : AppRouterConfig { override var componentRouter: Router? = null override var stack: List? = null override var snackbarHandler: SnackbarHandler? = null - override val isInitialized: MutableStateFlow = MutableStateFlow(false) + override val initializedState: MutableStateFlow = MutableStateFlow(false) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/transitions/RoutingTransitionAnimationFactory.kt b/app/src/main/java/com/tangem/tap/routing/transitions/RoutingTransitionAnimationFactory.kt index b1332ff1c8..a28e2ff15e 100644 --- a/app/src/main/java/com/tangem/tap/routing/transitions/RoutingTransitionAnimationFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/transitions/RoutingTransitionAnimationFactory.kt @@ -4,13 +4,10 @@ import androidx.compose.animation.core.CubicBezierEasing import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.tween import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.layout import com.arkivanov.decompose.extensions.compose.stack.animation.* import com.tangem.common.routing.AppRoute -import kotlin.compareTo -import kotlin.times object RoutingTransitionAnimationFactory { @@ -58,7 +55,7 @@ object RoutingTransitionAnimationFactory { @Suppress("MagicNumber") private fun slideAndFade(directions: Set? = null): StackAnimator { - val easing = CubicBezierEasing(0.55f, 0.0f, 0.0f, 1f) + val easing = CubicBezierEasing(a = 0.55f, b = 0.0f, c = 0.0f, d = 1f) return stackAnimator( animationSpec = tween(durationMillis = 400, easing = easing), diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 4f4eca31b6..ebdf08b11a 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -16,6 +16,7 @@ import com.tangem.features.details.component.DetailsComponent import com.tangem.features.disclaimer.api.components.DisclaimerComponent import com.tangem.features.home.api.HomeComponent 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.ManageTokensSource @@ -35,6 +36,7 @@ import com.tangem.features.swap.SwapComponent import com.tangem.features.swap.v2.api.SendWithSwapComponent import com.tangem.features.tangempay.components.TangemPayDetailsComponent import com.tangem.features.tangempay.components.TangemPayOnboardingComponent +import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.* import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.wallet.WalletEntryComponent import com.tangem.features.walletconnect.components.WalletConnectEntryComponent @@ -106,6 +108,7 @@ internal class ChildFactory @Inject constructor( private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory, private val tangemPayDetailsComponentFactory: TangemPayDetailsComponent.Factory, private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory, + private val kycComponentFactory: KycComponent.Factory, private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory, private val hotWalletFeatureToggles: HotWalletFeatureToggles, ) { @@ -183,10 +186,10 @@ internal class ChildFactory @Inject constructor( token = route.token, appCurrency = route.appCurrency, showPortfolio = route.showPortfolio, - analyticsParams = route.analyticsParams?.let { + analyticsParams = route.analyticsParams?.let { params -> MarketsTokenDetailsComponent.AnalyticsParams( - blockchain = it.blockchain, - source = it.source, + blockchain = params.blockchain, + source = params.source, ) }, ), @@ -597,10 +600,20 @@ internal class ChildFactory @Inject constructor( is AppRoute.TangemPayOnboarding -> { createComponentChild( context = context, - params = TangemPayOnboardingComponent.Params(route.deeplink), + params = when (val mode = route.mode) { + is AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding -> ContinueOnboarding + is AppRoute.TangemPayOnboarding.Mode.Deeplink -> Deeplink(deeplink = mode.deeplink) + }, componentFactory = tangemPayOnboardingComponentFactory, ) } + is AppRoute.Kyc -> { + createComponentChild( + context = context, + params = KycComponent.Params, + componentFactory = kycComponentFactory, + ) + } is AppRoute.YieldSupplyPromo -> { createComponentChild( context = context, diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 716edd6750..0a8969253b 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -376,8 +376,23 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class TangemPayOnboarding( - val deeplink: String, - ) : AppRoute(path = "/tangem_pay_onboarding/$deeplink") + val mode: Mode, + ) : AppRoute(path = "/tangem_pay_onboarding/$mode") { + + @Serializable + sealed class Mode { + @Serializable + data class Deeplink( + val deeplink: String, + ) : Mode() + + @Serializable + object ContinueOnboarding : Mode() + } + } + + @Serializable + data object Kyc : AppRoute(path = "/kyc") @Serializable data class YieldSupplyPromo( diff --git a/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt b/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt index c56e3718ef..d5f73cbee0 100644 --- a/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt +++ b/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt @@ -8,12 +8,12 @@ import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.common.test.domain.card.MockScanResponseFactory import com.tangem.common.test.domain.wallet.MockUserWalletFactory import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.domain.wallets.derivations.DerivationStyleProvider -import com.tangem.domain.wallets.derivations.derivationStyleProvider import com.tangem.domain.card.configs.GenericCardConfig import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.wallets.derivations.DerivationStyleProvider +import com.tangem.domain.wallets.derivations.derivationStyleProvider /** [REDACTED_AUTHOR] @@ -110,7 +110,7 @@ class MockCryptoCurrencyFactory(private val userWallet: UserWallet.Cold = defaul ) } - fun createToken(blockchain: Blockchain): CryptoCurrency { + fun createToken(blockchain: Blockchain): CryptoCurrency.Token { return factory.createToken( sdkToken = Token( name = "NEVER-MIND", diff --git a/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/MarketChart.kt b/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/MarketChart.kt index 992380b05e..e0eafb470d 100644 --- a/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/MarketChart.kt +++ b/common/ui-charts/src/main/kotlin/com/tangem/common/ui/charts/MarketChart.kt @@ -311,7 +311,6 @@ private fun MarketChartPreview( } val coroutineScope = rememberCoroutineScope() - val look by dataProducer.lookState.collectAsState() TangemThemePreview { val growingColor = TangemTheme.colors.icon.accent diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt index 6fcb02cc30..b49384c027 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt @@ -50,5 +50,4 @@ enum class ExpressStatusItemState { Done, Warning, Error, - ; } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationId.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationId.kt index 4b770200d0..2cc28a8b18 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationId.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationId.kt @@ -8,4 +8,5 @@ package com.tangem.common.ui.notifications */ enum class NotificationId(val key: String) { SendViaSwapTokenSelectorNotification("SendViaSwapTokenSelectorNotificationKey"), + EnablePushesReminderNotification("EnablePushesReminderNotification"), } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt index 1d4ff127d6..fb40d6dc2a 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt @@ -265,6 +265,11 @@ sealed class NotificationUM(val config: NotificationConfig) { title = resourceReference(id = R.string.selling_insufficient_balance_alert_title), subtitle = resourceReference(id = R.string.selling_insufficient_balance_alert_message), ) + + data class YieldSupplyIsActive(val tokenName: String) : Warning( + title = resourceReference(id = R.string.yield_module_balance_info_sheet_title, wrappedList(tokenName)), + subtitle = resourceReference(id = R.string.yield_module_balance_info_sheet_subtitle), + ) } open class Info( diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index 9c80a6a1b7..da0b138077 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -3,6 +3,7 @@ package com.tangem.common.ui.tokens import com.tangem.common.ui.R import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter +import com.tangem.core.ui.components.icons.IconTint import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter import com.tangem.core.ui.components.token.state.TokenItemState @@ -220,13 +221,12 @@ class TokenItemStateConverter( if (!status.getStakedBalance().isZero()) { TokenItemState.FiatAmountState.Content.IconUM( iconRes = R.drawable.ic_staking_24, - useAccentColor = true, + tint = IconTint.Accent, ).let(::add) } if (status.value.sources.total == StatusSource.ONLY_CACHE) { TokenItemState.FiatAmountState.Content.IconUM( iconRes = R.drawable.ic_error_sync_24, - useAccentColor = false, ).let(::add) } }.toImmutableList(), diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemTech.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemTech.kt index 8d2f1f19e2..2b9bb8b9f1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemTech.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemTech.kt @@ -2,12 +2,15 @@ package com.tangem.datasource.api.common.config import com.tangem.datasource.BuildConfig import com.tangem.datasource.api.common.AuthProvider +import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.utils.RequestHeader +import com.tangem.utils.ProviderSuspend import com.tangem.utils.info.AppInfoProvider import com.tangem.utils.version.AppVersionProvider /** TangemTech [ApiConfig] */ internal class TangemTech( + private val environmentConfigStorage: EnvironmentConfigStorage, private val appVersionProvider: AppVersionProvider, private val authProvider: AuthProvider, private val appInfoProvider: AppInfoProvider, @@ -38,29 +41,41 @@ internal class TangemTech( private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( environment = ApiEnvironment.DEV, baseUrl = "[REDACTED_ENV_URL]", - headers = createHeaders(), + headers = createHeaders(ApiEnvironment.DEV), ) private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( environment = ApiEnvironment.STAGE, baseUrl = "[REDACTED_ENV_URL]", - headers = createHeaders(), + headers = createHeaders(ApiEnvironment.STAGE), ) private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( environment = ApiEnvironment.MOCK, baseUrl = "[REDACTED_ENV_URL]", - headers = createHeaders(), + headers = createHeaders(ApiEnvironment.MOCK), ) private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( environment = ApiEnvironment.PROD, baseUrl = "https://api.tangem.org/", - headers = createHeaders(), + headers = createHeaders(ApiEnvironment.PROD), ) - private fun createHeaders() = buildMap { + private fun createHeaders(apiEnvironment: ApiEnvironment) = buildMap { + put(key = "api-key", value = ProviderSuspend { getApiKey(apiEnvironment) }) putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values) putAll(from = RequestHeader.AuthenticationHeader(authProvider).values) } + + private fun getApiKey(apiEnvironment: ApiEnvironment): String { + return when (apiEnvironment) { + ApiEnvironment.MOCK -> null + ApiEnvironment.DEV, + ApiEnvironment.DEV_2, + -> environmentConfigStorage.getConfigSync().tangemApiKeyDev + ApiEnvironment.STAGE -> environmentConfigStorage.getConfigSync().tangemApiKeyStage + ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().tangemApiKey + } ?: error("No tangem tech api config provided") + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt index f0de0bfdda..08703b2212 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt @@ -26,10 +26,10 @@ internal class DevApiConfigsManager( ) : MutableApiConfigsManager() { override val configs: StateFlow> - field = MutableStateFlow(value = getInitialConfigs()) + field = MutableStateFlow(value = getInitialConfigs()) override val isInitialized: StateFlow - field = MutableStateFlow(value = false) + field = MutableStateFlow(value = false) override fun initialize() { isInitialized.value = false diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/MockApiConfigsManager.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/MockApiConfigsManager.kt index 5df19d76e6..c423afc4c3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/MockApiConfigsManager.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/MockApiConfigsManager.kt @@ -22,7 +22,7 @@ internal class MockApiConfigsManager( ) : MutableApiConfigsManager() { override val configs: StateFlow> - field = MutableStateFlow(value = getInitialConfigs()) + field = MutableStateFlow(value = getInitialConfigs()) override val isInitialized: StateFlow = MutableStateFlow(value = true) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/MutableApiConfigsManager.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/MutableApiConfigsManager.kt index cdd9a6b0a2..d0998c8395 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/MutableApiConfigsManager.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/MutableApiConfigsManager.kt @@ -20,7 +20,7 @@ abstract class MutableApiConfigsManager : ApiConfigsManager { * These listeners are notified whenever an environment change occurs. */ protected val registerListeners: Set - field = mutableSetOf() + field = mutableSetOf() /** Change api environment [environment] by [id] */ abstract suspend fun changeEnvironment(id: String, environment: ApiEnvironment) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt index b753ffc387..87a22537d3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt @@ -9,6 +9,8 @@ import retrofit2.http.Header import retrofit2.http.POST import retrofit2.http.Query +private const val TX_HISTORY_PAGING_DEFAULT_LIMIT = 20 + @Suppress("TooManyFunctions") interface TangemPayApi { @@ -107,6 +109,13 @@ interface TangemPayApi { @Query("offset") offset: Int, ): ApiResponse + @GET("v1/customer/transactions") + suspend fun getTangemPayTxHistory( + @Header("Authorization") authHeader: String, + @Query("cursor") cursor: String?, + @Query("limit") limit: Int = TX_HISTORY_PAGING_DEFAULT_LIMIT, + ): ApiResponse + @GET("v1/customer/kyc") suspend fun getKycAccess(@Header("Authorization") authHeader: String): ApiResponse diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt index 6c60787d46..24b58f4b95 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt @@ -2,6 +2,7 @@ package com.tangem.datasource.api.pay.models.response import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import java.math.BigDecimal @JsonClass(generateAdapter = true) data class CustomerMeResponse( @@ -16,6 +17,8 @@ data class CustomerMeResponse( @Json(name = "product_instance") val productInstance: ProductInstance?, @Json(name = "payment_account") val paymentAccount: PaymentAccount?, @Json(name = "kyc") val kyc: Kyc?, + @Json(name = "card") val card: Card?, + @Json(name = "balance") val balance: Balance?, ) @JsonClass(generateAdapter = true) @@ -45,4 +48,25 @@ data class CustomerMeResponse( @Json(name = "review_answer") val reviewAnswer: String, @Json(name = "created_at") val createdAt: String, ) + + @JsonClass(generateAdapter = true) + data class Card( + @Json(name = "token") val token: String, + @Json(name = "expiration_month") val expirationMonth: Int, + @Json(name = "expiration_year") val expirationYear: Int, + @Json(name = "emboss_name") val embossName: String, + @Json(name = "card_type") val cardType: String, + @Json(name = "card_status") val cardStatus: String, + @Json(name = "card_number_end") val cardNumberEnd: String, + ) + + @JsonClass(generateAdapter = true) + data class Balance( + @Json(name = "currency") val currency: String, + @Json(name = "available_balance") val availableBalance: BigDecimal, + @Json(name = "credit_limit") val creditLimit: BigDecimal, + @Json(name = "pending_charges") val pendingCharges: BigDecimal, + @Json(name = "posted_charges") val postedCharges: BigDecimal, + @Json(name = "balance_due") val balanceDue: BigDecimal, + ) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/TangemPayTxHistoryResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/TangemPayTxHistoryResponse.kt new file mode 100644 index 0000000000..c6e87268e6 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/TangemPayTxHistoryResponse.kt @@ -0,0 +1,83 @@ +package com.tangem.datasource.api.pay.models.response + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.joda.time.DateTime +import java.math.BigDecimal + +@JsonClass(generateAdapter = true) +data class TangemPayTxHistoryResponse( + @Json(name = "error") val error: String?, + @Json(name = "result") val result: Result, +) { + @JsonClass(generateAdapter = true) + data class Result( + @Json(name = "transactions") val transactions: List, + ) + + @JsonClass(generateAdapter = true) + data class Transaction( + @Json(name = "id") val id: String, // UUID (used as cursor for pagination) + @Json(name = "type") val type: String, // "SPEND", "COLLATERAL", "PAYMENT", "FEE" + @Json(name = "spend") val spend: Spend? = null, + @Json(name = "collateral") val collateral: Collateral? = null, + @Json(name = "payment") val payment: Payment? = null, + @Json(name = "fee") val fee: Fee? = null, + ) + + @JsonClass(generateAdapter = true) + data class Spend( + @Json(name = "amount") val amount: BigDecimal, + @Json(name = "currency") val currency: String, + @Json(name = "local_amount") val localAmount: BigDecimal? = null, + @Json(name = "local_currency") val localCurrency: String? = null, + @Json(name = "authorized_amount") val authorizedAmount: BigDecimal? = null, + @Json(name = "authorization_method") val authorizationMethod: String? = null, + @Json(name = "memo") val memo: String? = null, + @Json(name = "receipt") val receipt: Boolean? = null, + @Json(name = "merchant_name") val merchantName: String? = null, + @Json(name = "merchant_category") val merchantCategory: String? = null, + @Json(name = "merchant_category_code") val merchantCategoryCode: String? = null, + @Json(name = "merchant_id") val merchantId: String? = null, + @Json(name = "enriched_merchant_icon") val enrichedMerchantIcon: String? = null, + @Json(name = "enriched_merchant_name") val enrichedMerchantName: String? = null, + @Json(name = "enriched_merchant_category") val enrichedMerchantCategory: String? = null, + @Json(name = "card_id") val cardId: String? = null, + @Json(name = "card_type") val cardType: String? = null, + @Json(name = "status") val status: String? = null, + @Json(name = "declined_reason") val declinedReason: String? = null, + @Json(name = "authorized_at") val authorizedAt: DateTime? = null, + @Json(name = "posted_at") val postedAt: DateTime? = null, + ) + + @JsonClass(generateAdapter = true) + data class Collateral( + @Json(name = "amount") val amount: BigDecimal, + @Json(name = "currency") val currency: String, + @Json(name = "memo") val memo: String? = null, + @Json(name = "chain_id") val chainId: Long? = null, + @Json(name = "wallet_address") val walletAddress: String? = null, + @Json(name = "transaction_hash") val transactionHash: String? = null, + @Json(name = "posted_at") val postedAt: DateTime? = null, + ) + + @JsonClass(generateAdapter = true) + data class Payment( + @Json(name = "amount") val amount: BigDecimal, + @Json(name = "currency") val currency: String, + @Json(name = "memo") val memo: String? = null, + @Json(name = "chain_id") val chainId: Long? = null, + @Json(name = "wallet_address") val walletAddress: String? = null, + @Json(name = "transaction_hash") val transactionHash: String? = null, + @Json(name = "status") val status: String? = null, + @Json(name = "posted_at") val postedAt: DateTime? = null, + ) + + @JsonClass(generateAdapter = true) + data class Fee( + @Json(name = "amount") val amount: BigDecimal, + @Json(name = "currency") val currency: String, + @Json(name = "description") val description: String? = null, + @Json(name = "posted_at") val postedAt: DateTime? = null, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt index b58c058063..b3cef83f29 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt @@ -198,6 +198,7 @@ data class YieldDTO( enum class RewardTypeDTO { @Json(name = "apy") APY, // compound rate + @Json(name = "apr") APR, // simple rate, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/SeedPhraseNotificationDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/SeedPhraseNotificationDTO.kt index 3184e87ead..3ef25cc80f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/SeedPhraseNotificationDTO.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/SeedPhraseNotificationDTO.kt @@ -24,7 +24,5 @@ data class SeedPhraseNotificationDTO(val status: Status) { @Json(name = "accepted") ACCEPTED, - - ; } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt index f977ce542b..04961c598a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt @@ -42,10 +42,12 @@ internal object ApiConfigsModule { @Provides @IntoSet fun provideTangemTechConfig( + environmentConfigStorage: EnvironmentConfigStorage, appVersionProvider: AppVersionProvider, authProvider: AuthProvider, appInfoProvider: AppInfoProvider, ): ApiConfig = TangemTech( + environmentConfigStorage = environmentConfigStorage, appVersionProvider = appVersionProvider, authProvider = authProvider, appInfoProvider = appInfoProvider, diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/TxHistoryItemsStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/TxHistoryItemsStoreModule.kt index aa6dd40222..283ea6c473 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/TxHistoryItemsStoreModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/TxHistoryItemsStoreModule.kt @@ -3,6 +3,8 @@ package com.tangem.datasource.di import com.tangem.datasource.local.datastore.RuntimeDataStore import com.tangem.datasource.local.txhistory.DefaultTxHistoryItemsStore import com.tangem.datasource.local.txhistory.TxHistoryItemsStore +import com.tangem.datasource.local.visa.DefaultTangemPayTxHistoryItemsStore +import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -20,4 +22,12 @@ internal object TxHistoryItemsStoreModule { dataStore = RuntimeDataStore(), ) } + + @Provides + @Singleton + fun provideTangemPayTxHistoryItemsStore(): TangemPayTxHistoryItemsStore { + return DefaultTangemPayTxHistoryItemsStore( + dataStore = RuntimeDataStore(), + ) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/EnvironmentConfig.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/EnvironmentConfig.kt index 5f44b31c6e..a960c5b593 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/EnvironmentConfig.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/EnvironmentConfig.kt @@ -17,4 +17,7 @@ data class EnvironmentConfig( val devExpress: ExpressModel? = null, val stakeKitApiKey: String? = null, val blockAidApiKey: String? = null, + val tangemApiKey: String? = null, + val tangemApiKeyDev: String? = null, + val tangemApiKeyStage: String? = null, ) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt index a70611411d..4fcac008f1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt @@ -26,6 +26,9 @@ internal object EnvironmentConfigConverter : Converter>>, +) : TangemPayTxHistoryItemsStore, + StringKeyDataStoreDecorator>>(dataStore) { + override fun provideStringKey(key: UserWalletId): String = key.stringValue + + override suspend fun getSyncOrNull(key: UserWalletId, cursor: String): List? { + val storedValue = getSyncOrNull(key) + return storedValue?.get(cursor) + } + + override suspend fun store(key: UserWalletId, cursor: String, value: List) { + val oldValue = getSyncOrNull(key).orEmpty() + val newValue = oldValue.toMutableMap().apply { put(cursor, value) } + store(key, newValue) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt index b988222b09..edf36a599f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt @@ -8,9 +8,5 @@ interface TangemPayStorage { suspend fun getAuthTokens(customerWalletAddress: String): VisaAuthTokens? - suspend fun storeCustomerWalletAddress(customerWalletAddress: String) - - suspend fun getCustomerWalletAddress(): String? - suspend fun clear(customerWalletAddress: String) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayTxHistoryItemsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayTxHistoryItemsStore.kt new file mode 100644 index 0000000000..c327023e6b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayTxHistoryItemsStore.kt @@ -0,0 +1,13 @@ +package com.tangem.datasource.local.visa + +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.visa.model.TangemPayTxHistoryItem + +interface TangemPayTxHistoryItemsStore { + + suspend fun getSyncOrNull(key: UserWalletId, cursor: String): List? + + suspend fun remove(key: UserWalletId) + + suspend fun store(key: UserWalletId, cursor: String, value: List) +} \ No newline at end of file diff --git a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/ApiConfigTest.kt b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/ApiConfigTest.kt index 651e704cdc..e46f600973 100644 --- a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/ApiConfigTest.kt +++ b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/ApiConfigTest.kt @@ -39,6 +39,7 @@ class ApiConfigTest { } ApiConfig.ID.TangemTech -> { TangemTech( + environmentConfigStorage = mockk(), appVersionProvider = mockk(), authProvider = mockk(), appInfoProvider = mockk(), diff --git a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt index 61d25e29b1..c4e9e81ecf 100644 --- a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt +++ b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt @@ -86,6 +86,7 @@ internal class ProdApiConfigsManagerTest { } ApiConfig.ID.TangemTech -> { TangemTech( + environmentConfigStorage = environmentConfigStorage, appVersionProvider = appVersionProvider, authProvider = appAuthProvider, appInfoProvider = appInfoProvider, diff --git a/core/pagination/src/main/java/com/tangem/pagination/fetcher/CursorBatchFetcher.kt b/core/pagination/src/main/java/com/tangem/pagination/fetcher/CursorBatchFetcher.kt new file mode 100644 index 0000000000..e41f84d55d --- /dev/null +++ b/core/pagination/src/main/java/com/tangem/pagination/fetcher/CursorBatchFetcher.kt @@ -0,0 +1,92 @@ +package com.tangem.pagination.fetcher + +import com.tangem.pagination.BatchFetchResult +import com.tangem.pagination.exception.EndOfPaginationException +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.MutableStateFlow + +/** + * First page: cursor = null + * Next pages: cursor = cursorFromItem(lastItemOfPreviousPage) + */ +class CursorBatchFetcher( + private val prefetchDistance: Int, + private val batchSize: Int, + private val subFetcher: SubFetcher, + private val cursorFromItem: (TItem) -> String, +) : BatchFetcher> { + + data class Request( + val limit: Int, + val cursor: String?, + val params: TRequestParams, + ) + + fun interface SubFetcher { + suspend fun fetch( + request: Request, + lastResult: BatchFetchResult>?, + isFirstBatchFetching: Boolean, + ): BatchFetchResult> + } + + private val lastRequest = MutableStateFlow?>(null) + + override suspend fun fetchFirst(requestParams: TRequestParams): BatchFetchResult> { + val request = Request( + cursor = null, + limit = prefetchDistance, + params = requestParams, + ) + + val result = runCatching { + subFetcher.fetch(request = request, lastResult = null, isFirstBatchFetching = true) + }.getOrElse { + currentCoroutineContext().ensureActive() + return BatchFetchResult.Error(it) + } + + lastRequest.value = request + return result + } + + override suspend fun fetchNext( + overrideRequestParams: TRequestParams?, + lastResult: BatchFetchResult>, + ): BatchFetchResult> { + val lastRequest = requireNotNull(lastRequest.value) { "fetchFirst() must be called before fetchNext()" } + + if (lastResult is BatchFetchResult.Success && lastResult.last && overrideRequestParams == null) { + return BatchFetchResult.Error(EndOfPaginationException()) + } + + val nextReq: Request = + if (lastResult is BatchFetchResult.Success>) { + val items = lastResult.data + if (items.isEmpty()) { + return BatchFetchResult.Error(EndOfPaginationException()) + } + + val nextCursor = cursorFromItem(items.last()) + + Request( + cursor = nextCursor, + limit = batchSize, + params = overrideRequestParams ?: lastRequest.params, + ) + } else { + lastRequest.copy(limit = batchSize, params = overrideRequestParams ?: lastRequest.params) + } + + val result = runCatching { + subFetcher.fetch(request = nextReq, lastResult = lastResult, isFirstBatchFetching = false) + }.getOrElse { + currentCoroutineContext().ensureActive() + return BatchFetchResult.Error(it) + } + + this.lastRequest.value = nextReq + return result + } +} \ No newline at end of file diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index e12a6bbfa6..bbdf7ef9a1 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1733,7 +1733,6 @@ Total earnings Transfers to Aave Explore Aave - Your %s is deposited in Aave This is the current supply fee on %s. The live cost will be shown on the Receive Screen. Current fee All future %s top-ups will be supplied to Aave automatically, with the transaction fee deducted. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt index 27c758f8b7..c5807f7912 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -68,9 +68,9 @@ fun CardWithIcon( internal fun IconWithTitleAndDescription( title: String, description: String?, + iconBackground: Color = TangemTheme.colors.background.secondary, icon: @Composable () -> Unit, additionalContent: @Composable () -> Unit = {}, - iconBackground: Color = TangemTheme.colors.background.secondary, ) { Row( modifier = Modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/DialogFullScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/components/DialogFullScreen.kt new file mode 100644 index 0000000000..6f70db0ba1 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/DialogFullScreen.kt @@ -0,0 +1,94 @@ +package com.tangem.core.ui.components + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.os.Build +import android.view.View +import android.view.Window +import android.view.WindowManager +import android.widget.FrameLayout +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.compose.ui.window.DialogWindowProvider +import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.tangem.core.ui.res.LocalIsInDarkTheme + +@Composable +fun DialogFullScreen( + onDismissRequest: () -> Unit, + properties: DialogProperties = DialogProperties(), + content: @Composable () -> Unit, +) { + Dialog( + onDismissRequest = onDismissRequest, + properties = DialogProperties( + dismissOnBackPress = properties.dismissOnBackPress, + dismissOnClickOutside = properties.dismissOnClickOutside, + securePolicy = properties.securePolicy, + usePlatformDefaultWidth = true, // must be true as a part of work around + decorFitsSystemWindows = false, + ), + content = { + val activityWindow = getActivityWindow() + val dialogWindow = getDialogWindow() + val parentView = LocalView.current.parent as View + SideEffect { + if (activityWindow != null && dialogWindow != null) { + val attributes = WindowManager.LayoutParams().apply { + copyFrom(activityWindow.attributes) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + } else { + flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + } + type = dialogWindow.attributes.type + } + + dialogWindow.attributes = attributes + parentView.layoutParams = + FrameLayout.LayoutParams( + activityWindow.decorView.width, + activityWindow.decorView.height, + ) + } + } + + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + val systemUiController = rememberSystemUiController(getActivityWindow()) + val dialogSystemUiController = rememberSystemUiController(getDialogWindow()) + + SideEffect { + systemUiController.setSystemBarsColor(color = Color.Transparent) + dialogSystemUiController.setSystemBarsColor(color = Color.Transparent) + } + } + + SystemBarsIconsDisposable(darkIcons = LocalIsInDarkTheme.current.not()) + + Surface(modifier = Modifier.fillMaxSize(), color = Color.Transparent) { + content() + } + }, + ) +} + +// Window utils +@Composable +private fun getDialogWindow(): Window? = (LocalView.current.parent as? DialogWindowProvider)?.window + +@Composable +private fun getActivityWindow(): Window? = LocalView.current.context.getActivityWindow() + +private tailrec fun Context.getActivityWindow(): Window? = when (this) { + is Activity -> window + is ContextWrapper -> baseContext.getActivityWindow() + else -> null +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/FadeModifier.kt b/core/ui/src/main/java/com/tangem/core/ui/components/FadeModifier.kt index 03d9ba11c0..fcf6361bca 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/FadeModifier.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/FadeModifier.kt @@ -75,7 +75,7 @@ fun Modifier.bottomFade( ) enum class FadePosition { - TOP, BOTTOM, LEFT, RIGHT; + TOP, BOTTOM, LEFT, RIGHT } @Stable diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlock.kt index e5c28bf0a6..e42d2ad4fc 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlock.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlock.kt @@ -169,12 +169,12 @@ private fun Preview_Tree() { }, content = { ArrowRowItems( - itemPadding = PaddingValues(vertical = TangemTheme.dimens.spacing4), items = persistentListOf( stringReference("Fist item"), stringReference("Second item"), stringReference("Third item"), ), + itemPadding = PaddingValues(vertical = TangemTheme.dimens.spacing4), rootContent = { PreviewItem(stringReference("Root")) }, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlockContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlockContent.kt index 423100d2e9..a39c6b7783 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlockContent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlockContent.kt @@ -16,11 +16,11 @@ import kotlinx.collections.immutable.toImmutableList @Composable inline fun InformationBlockContentScope.ListItems( items: ImmutableList, - itemContent: @Composable BoxScope.(T) -> Unit, modifier: Modifier = Modifier, itemPadding: PaddingValues = PaddingValues(all = TangemTheme.dimens.spacing0), horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, verticalArragement: Arrangement.Vertical = Arrangement.Top, + itemContent: @Composable BoxScope.(T) -> Unit, ) { Column( modifier = modifier.fillMaxWidth(), @@ -42,10 +42,10 @@ inline fun InformationBlockContentScope.ListItems( @Composable inline fun InformationBlockContentScope.GridItems( items: ImmutableList, - itemContent: @Composable BoxScope.(T) -> Unit, modifier: Modifier = Modifier, verticalAlignment: Alignment.Vertical = Alignment.Top, horizontalArragement: Arrangement.Horizontal = Arrangement.Start, + itemContent: @Composable BoxScope.(T) -> Unit, ) { val rowItems by remember(items) { derivedStateOf { @@ -81,10 +81,10 @@ inline fun InformationBlockContentScope.GridItems( @Composable inline fun InformationBlockContentScope.ArrowRowItems( items: ImmutableList, - rootContent: @Composable BoxScope.() -> Unit, - itemContent: @Composable BoxScope.(T) -> Unit, modifier: Modifier = Modifier, itemPadding: PaddingValues = PaddingValues(all = TangemTheme.dimens.spacing0), + rootContent: @Composable BoxScope.() -> Unit, + itemContent: @Composable BoxScope.(T) -> Unit, ) { Column( modifier = modifier.fillMaxWidth(), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheet.kt index 196a5f2012..b239dfc92b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheet.kt @@ -48,10 +48,10 @@ const val MODAL_SHEET_MAX_HEIGHT = 0.8f inline fun TangemModalBottomSheet( config: TangemBottomSheetConfig, containerColor: Color = TangemTheme.colors.background.primary, + noinline onBack: (() -> Unit)? = null, skipPartiallyExpanded: Boolean = true, dismissOnClickOutside: Boolean = true, scrollableContent: Boolean = true, - noinline onBack: (() -> Unit)? = null, crossinline title: @Composable BoxScope.(T) -> Unit = {}, crossinline content: @Composable ColumnScope.(T) -> Unit, ) { @@ -202,9 +202,9 @@ inline fun BsContent( inline fun BasicModalBottomSheet( config: TangemBottomSheetConfig, sheetState: SheetState, + modifier: Modifier = Modifier, noinline onBack: (() -> Unit)? = null, noinline bsContent: @Composable ColumnScope.() -> Unit, - modifier: Modifier = Modifier, ) { if (onBack != null) { ModalBottomSheetWithBackHandling( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt index 3826cc503f..6506a16d14 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt @@ -143,11 +143,11 @@ inline fun BasicModalBottomSheetWit config: TangemBottomSheetConfig, sheetState: SheetState, containerColor: Color, + modifier: Modifier = Modifier, noinline onBack: (() -> Unit)? = null, crossinline title: @Composable BoxScope.(T) -> Unit, crossinline content: @Composable (T) -> Unit, noinline footer: @Composable (BoxScope.(T) -> Unit)?, - modifier: Modifier = Modifier, ) { val model = config.content as? T ?: return diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/sheet/TangemBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/sheet/TangemBottomSheet.kt index 1ecce16ab7..26ad428e58 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/sheet/TangemBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/sheet/TangemBottomSheet.kt @@ -152,10 +152,10 @@ inline fun BasicBottomSheet( sheetState: SheetState, containerColor: Color, addBottomInsets: Boolean, + modifier: Modifier = Modifier, noinline onBack: (() -> Unit)? = null, crossinline title: @Composable (BoxScope.(T) -> Unit), crossinline content: @Composable (ColumnScope.(T) -> Unit), - modifier: Modifier = Modifier, ) { val model = config.content as? T ?: return diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt index 8f5326a498..d84ac5aacb 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt @@ -22,7 +22,7 @@ import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.test.TokenDetailsScreenTestTags +import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -36,7 +36,7 @@ fun HorizontalActionChips( LazyRow( modifier = modifier .fillMaxWidth() - .testTag(TokenDetailsScreenTestTags.HORIZONTAL_ACTION_CHIPS), + .testTag(BaseActionButtonsBlockTestTags.HORIZONTAL_ACTION_CHIPS), horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8), verticalAlignment = Alignment.CenterVertically, contentPadding = contentPadding, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index a7fa9386f9..110fc5cc3a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -34,7 +34,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.test.TokenDetailsScreenTestTags +import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags /** * Rounded action button @@ -100,7 +100,7 @@ fun ActionButton( ), ) }, - modifier = modifier.testTag(TokenDetailsScreenTestTags.ACTION_BUTTON), + modifier = modifier, color = color, containerColor = containerColor, ) @@ -111,10 +111,10 @@ fun ActionButton( fun ActionBaseButton( config: ActionButtonConfig, shape: RoundedCornerShape, - content: @Composable (modifier: Modifier) -> Unit, modifier: Modifier = Modifier, color: Color = TangemTheme.colors.button.secondary, containerColor: Color = TangemTheme.colors.background.secondary, + content: @Composable (modifier: Modifier) -> Unit, ) { val context = LocalContext.current val backgroundColor by animateColorAsState( @@ -145,7 +145,8 @@ fun ActionBaseButton( } }, ) - .background(color = backgroundColor), + .background(color = backgroundColor) + .testTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON), ) { content(Modifier.align(Alignment.Center)) @@ -163,9 +164,9 @@ fun ActionBaseButton( @Composable fun ActionButtonContent( config: ActionButtonConfig, - text: @Composable (Color) -> Unit, modifier: Modifier = Modifier, paddingBetweenIconAndText: Dp = 8.dp, + text: @Composable (Color) -> Unit, ) { Row( modifier = modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/DefaultCurrencyIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/DefaultCurrencyIcon.kt index bc9b5f2707..630bd4cc12 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/DefaultCurrencyIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/DefaultCurrencyIcon.kt @@ -24,8 +24,8 @@ internal inline fun DefaultCurrencyIcon( size: Dp, alpha: Float, colorFilter: ColorFilter?, - crossinline errorIcon: @Composable () -> Unit, modifier: Modifier = Modifier, + crossinline errorIcon: @Composable () -> Unit, ) { var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) } var isBackgroundColorDefined by remember { mutableStateOf(false) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/ContentIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/ContentIcon.kt index 02e42068d6..e8fa3055bf 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/ContentIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/ContentIcon.kt @@ -113,8 +113,8 @@ private fun TokenIcon( url: String?, alpha: Float, colorFilter: ColorFilter?, - errorIcon: @Composable () -> Unit, modifier: Modifier = Modifier, + errorIcon: @Composable () -> Unit, ) { if (url == null) { errorIcon() diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SearchBar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SearchBar.kt index 94f6b64e78..f7ec1628af 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SearchBar.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SearchBar.kt @@ -103,11 +103,11 @@ fun SearchBar( @OptIn(ExperimentalMaterial3Api::class) private fun DecorationBox( state: SearchBarUM, - innerTextField: @Composable () -> Unit, interactionSource: MutableInteractionSource, colors: TextFieldColors, focusManager: FocusManager, keyboardController: SoftwareKeyboardController?, + innerTextField: @Composable () -> Unit, ) { TextFieldDefaults.DecorationBox( value = state.query, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index 1616968df4..e5c732ea8c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -121,12 +121,12 @@ fun SimpleTextField( @Composable private fun SimpleTextPlaceholder( - placeholder: TextReference?, value: String, textStyle: TextStyle, centered: Boolean, - textValue: @Composable () -> Unit, + placeholder: TextReference?, color: Color = TangemTheme.colors.text.disabled, + textValue: @Composable () -> Unit, ) { Box(contentAlignment = if (centered) Alignment.Center else Alignment.TopStart) { if (value.isBlank() && placeholder != null) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/icons/IconTint.kt b/core/ui/src/main/java/com/tangem/core/ui/components/icons/IconTint.kt new file mode 100644 index 0000000000..db6a607f67 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/icons/IconTint.kt @@ -0,0 +1,10 @@ +package com.tangem.core.ui.components.icons + +import androidx.compose.runtime.Stable + +@Stable +enum class IconTint { + Accent, + Warning, + Inactive, +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/icons/identicon/Blockies.kt b/core/ui/src/main/java/com/tangem/core/ui/components/icons/identicon/Blockies.kt index f1ba4a748e..83d46d09e3 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/icons/identicon/Blockies.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/icons/identicon/Blockies.kt @@ -58,8 +58,8 @@ internal data class Blockies( } private fun dataFromSeed(seed: MutableList) = MutableList(SIZE * SIZE) { DEFAULT_VALUE_F }.apply { - (0 until SIZE).forEach { row -> - (0 until HALF_SIZE).forEach { column -> + for (row in 0 until SIZE) { + for (column in 0 until HALF_SIZE) { val value = floor(nextSeed(seed) * PROBABILITY_COLOR) this[row * SIZE + column] = value this[(row + 1) * SIZE - column - 1] = value diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/ArrowRow.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/ArrowRow.kt index 67ef1ac47d..36d9892d0d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/ArrowRow.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/ArrowRow.kt @@ -21,9 +21,9 @@ import com.tangem.core.ui.utils.* @Composable inline fun ArrowRow( isLastItem: Boolean, - content: @Composable() (BoxScope.() -> Unit), modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(all = TangemTheme.dimens.spacing0), + content: @Composable() (BoxScope.() -> Unit), ) { val density = LocalDensity.current.density val defaultRowHeight = TangemTheme.dimens.size0 diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/BlockchainRow.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/BlockchainRow.kt index df2cdf37a9..d89d6710c1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/BlockchainRow.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/BlockchainRow.kt @@ -27,7 +27,7 @@ private const val DISABLED_ICON_ALPHA = 0.4f * [Figma Component](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2737-2800&t=ewlXfWwbDnRhjw4B-4) * */ @Composable -fun BlockchainRow(model: BlockchainRowUM, action: @Composable BoxScope.() -> Unit, modifier: Modifier = Modifier) { +fun BlockchainRow(model: BlockchainRowUM, modifier: Modifier = Modifier, action: @Composable BoxScope.() -> Unit) { RowContentContainer( modifier = modifier .heightIn(min = TangemTheme.dimens.size52) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/ChainRow.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/ChainRow.kt index a9020f35ae..ffe4aae9e2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/ChainRow.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/ChainRow.kt @@ -53,10 +53,10 @@ fun ChainRow(model: ChainRowUM, modifier: Modifier = Modifier, action: @Composab @Composable inline fun ChainRowContainer( + modifier: Modifier = Modifier, icon: @Composable BoxScope.() -> Unit, text: @Composable BoxScope.() -> Unit, action: @Composable BoxScope.() -> Unit, - modifier: Modifier = Modifier, ) { RowContentContainer( modifier = modifier diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/NetworkTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/NetworkTitle.kt index 383854fd65..590e93ddb5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/NetworkTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/NetworkTitle.kt @@ -29,8 +29,8 @@ import com.tangem.core.ui.res.TangemThemePreview */ @Composable fun NetworkTitle( - title: @Composable BoxScope.() -> Unit, modifier: Modifier = Modifier, + title: @Composable BoxScope.() -> Unit, action: (@Composable BoxScope.() -> Unit)? = null, ) { val minHeight = if (action == null) TangemTheme.dimens.size36 else TangemTheme.dimens.size40 diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/RowComponents.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/RowComponents.kt index f07edb1802..2c55637c6e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/RowComponents.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/RowComponents.kt @@ -13,11 +13,11 @@ import com.tangem.core.ui.res.TangemTheme @Composable inline fun RowContentContainer( + modifier: Modifier = Modifier, + horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(TangemTheme.dimens.spacing8), icon: @Composable BoxScope.() -> Unit, text: @Composable BoxScope.() -> Unit, action: @Composable BoxScope.() -> Unit, - modifier: Modifier = Modifier, - horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { Row( modifier = modifier.fillMaxWidth(), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt index 48f79e027f..e9347aa9b6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt @@ -122,16 +122,18 @@ fun ShowcaseButtons( private fun Showcase_Preview() { TangemThemePreview { Showcase( - headerIconRes = R.drawable.ic_notifications_unread_24, + headerIconRes = R.drawable.ic_notification_56, headerText = resourceReference(R.string.user_push_notification_agreement_header), showcaseItems = persistentListOf( ShowcaseItemModel( - R.drawable.ic_rocket_launch_24, - resourceReference(R.string.user_push_notification_agreement_argument_one), + iconRes = R.drawable.ic_rocket_launch_24, + title = resourceReference(R.string.user_push_notification_agreement_argument_one_title), + subTitle = resourceReference(R.string.user_push_notification_agreement_argument_one_subtitle), ), ShowcaseItemModel( - R.drawable.ic_storefront_24, - resourceReference(R.string.user_push_notification_agreement_argument_two), + iconRes = R.drawable.ic_storefront_24, + title = resourceReference(R.string.user_push_notification_agreement_argument_two_title), + subTitle = resourceReference(R.string.user_push_notification_agreement_argument_two_subtitle), ), ), primaryButton = ShowcaseButtonModel(resourceReference(R.string.common_allow), {}), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt index 336172a208..c4ff5a39e2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt @@ -63,7 +63,8 @@ fun ShowcaseContent( repeat(showcaseItems.size) { index -> ShowcaseItem( iconRes = showcaseItems[index].iconRes, - text = showcaseItems[index].text, + title = showcaseItems[index].title, + subtitle = showcaseItems[index].subTitle, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt index 2f098c6720..4fe07dc38e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt @@ -1,33 +1,47 @@ package com.tangem.core.ui.components.showcase import androidx.annotation.DrawableRes -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme @Composable -internal fun ShowcaseItem(@DrawableRes iconRes: Int, text: TextReference) { - Row { +internal fun ShowcaseItem(@DrawableRes iconRes: Int, title: TextReference, subtitle: TextReference) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { Icon( painter = painterResource(id = iconRes), contentDescription = null, tint = TangemTheme.colors.icon.primary1, ) - Text( - text = text.resolveReference(), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.secondary, + Column( modifier = Modifier .fillMaxWidth() .padding(start = TangemTheme.dimens.spacing20), - ) + verticalArrangement = Arrangement.spacedBy(3.dp), + ) { + Text( + text = title.resolveReference(), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + modifier = Modifier.fillMaxWidth(), + ) + + Text( + text = subtitle.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + modifier = Modifier.fillMaxWidth(), + ) + } } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt index f9865aab9e..cac2da63c0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt @@ -5,5 +5,6 @@ import com.tangem.core.ui.extensions.TextReference data class ShowcaseItemModel( @DrawableRes val iconRes: Int, - val text: TextReference, + val title: TextReference, + val subTitle: TextReference, ) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt index 9a4585eb59..7bb297765a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt @@ -22,6 +22,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.audits.AuditLabelUM import com.tangem.core.ui.components.currency.icon.CurrencyIcon import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.icons.IconTint import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.token.internal.* import com.tangem.core.ui.components.token.state.TokenItemState @@ -488,7 +489,14 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider TangemTheme.colors.icon.accent + IconTint.Warning -> TangemTheme.colors.icon.attention + IconTint.Inactive -> TangemTheme.colors.icon.inactive }, contentDescription = null, ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt index 7f379e09a6..3fadd11e81 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt @@ -2,7 +2,9 @@ package com.tangem.core.ui.components.token.internal import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -13,8 +15,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp import com.tangem.core.ui.R import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.components.token.state.TokenItemState.TitleState as TokenTitleState @@ -56,6 +60,11 @@ private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Mo hasPending = state.hasPending, modifier = Modifier.align(alignment = Alignment.CenterVertically), ) + + YieldSupplyApyLabel( + apy = state.earnApy, + modifier = Modifier.align(alignment = Alignment.CenterVertically), + ) } } @@ -71,6 +80,25 @@ private fun CurrencyNameText(name: String, isAvailable: Boolean, modifier: Modif ) } +@Composable +private fun YieldSupplyApyLabel(apy: TextReference?, modifier: Modifier = Modifier) { + AnimatedVisibility(visible = apy != null, modifier = modifier) { + Box( + modifier = modifier.background( + color = TangemTheme.colors.text.accent.copy(alpha = 0.1f), + shape = TangemTheme.shapes.roundedCornersSmall2, + ), + ) { + Text( + text = apy?.resolveReference().orEmpty(), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.accent, + modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp), + ) + } + } +} + @Composable private fun PendingTransactionImage(hasPending: Boolean, modifier: Modifier = Modifier) { AnimatedVisibility(visible = hasPending, modifier = modifier) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt index be357d5ebf..f86a303999 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt @@ -3,6 +3,7 @@ package com.tangem.core.ui.components.token.state import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.audits.AuditLabelUM import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.icons.IconTint import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.extensions.TextReference import kotlinx.collections.immutable.ImmutableList @@ -167,6 +168,7 @@ sealed class TokenItemState { val text: TextReference, val hasPending: Boolean = false, val isAvailable: Boolean = true, + val earnApy: TextReference? = null, ) : TitleState() data object Loading : TitleState() @@ -204,7 +206,7 @@ sealed class TokenItemState { data class IconUM( val iconRes: Int, - val useAccentColor: Boolean, + val tint: IconTint = IconTint.Inactive, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tooltip/TangemTooltip.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tooltip/TangemTooltip.kt index 395b09aed0..7a54751d4b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tooltip/TangemTooltip.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tooltip/TangemTooltip.kt @@ -24,9 +24,9 @@ import kotlinx.coroutines.launch @Composable fun TangemTooltip( text: String, - content: @Composable (Modifier) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + content: @Composable (Modifier) -> Unit, ) { InternalTangemTooltip( modifier = modifier, @@ -46,9 +46,9 @@ fun TangemTooltip( @Composable fun TangemTooltip( text: AnnotatedString, - content: @Composable (Modifier) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + content: @Composable (Modifier) -> Unit, ) { InternalTangemTooltip( modifier = modifier, @@ -68,10 +68,10 @@ fun TangemTooltip( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun InternalTangemTooltip( - tooltipContent: @Composable () -> Unit, - content: @Composable (Modifier) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + tooltipContent: @Composable () -> Unit, + content: @Composable (Modifier) -> Unit, ) { val tooltipState = rememberTooltipState(isPersistent = true) val coroutineScope = rememberCoroutineScope() diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt index a6d96d536d..dfb6f1339c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/empty/EmptyTransactionBlock.kt @@ -9,6 +9,7 @@ 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.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -18,6 +19,7 @@ import com.tangem.core.ui.components.buttons.actions.ActionButton import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.test.EmptyTransactionBlockTestTags /** * Placeholder for transaction's block without content @@ -31,19 +33,24 @@ fun EmptyTransactionBlock(state: EmptyTransactionsBlockState, modifier: Modifier modifier = modifier .clip(TangemTheme.shapes.roundedCornersXMedium) .background(color = TangemTheme.colors.background.primary) - .padding(vertical = TangemTheme.dimens.spacing24), + .padding(vertical = TangemTheme.dimens.spacing24) + .testTag(EmptyTransactionBlockTestTags.BLOCK), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24), horizontalAlignment = Alignment.CenterHorizontally, ) { Icon( - modifier = Modifier.size(TangemTheme.dimens.size64), + modifier = Modifier + .size(TangemTheme.dimens.size64) + .testTag(EmptyTransactionBlockTestTags.ICON), painter = painterResource(id = state.iconRes), tint = TangemTheme.colors.icon.inactive, contentDescription = null, ) Text( - modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing32), + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing32) + .testTag(EmptyTransactionBlockTestTags.TEXT), textAlign = TextAlign.Center, text = state.text.resolveReference(), style = TangemTheme.typography.body2, @@ -51,7 +58,9 @@ fun EmptyTransactionBlock(state: EmptyTransactionsBlockState, modifier: Modifier ) Buttons( - modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing18), + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing18) + .testTag(EmptyTransactionBlockTestTags.EXPLORE_BUTTON), state = state.buttonsState, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableBottomSheetComponent.kt b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableBottomSheetComponent.kt index 6128a71c4a..d6f98918b8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableBottomSheetComponent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableBottomSheetComponent.kt @@ -10,13 +10,16 @@ interface ComposableBottomSheetComponent { @Composable fun BottomSheet() -} -fun getEmptyComposableBottomSheetComponent() = EmptyComposableBottomSheetComponent + companion object { + val EMPTY = EmptyComposableBottomSheetComponent + } +} object EmptyComposableBottomSheetComponent : ComposableBottomSheetComponent { override fun dismiss() {} @Composable - override fun BottomSheet() {} + override fun BottomSheet() { + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableContentComponent.kt b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableContentComponent.kt index b242250e28..9e861ebc84 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableContentComponent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableContentComponent.kt @@ -9,9 +9,11 @@ fun interface ComposableContentComponent { @Composable fun Content(modifier: Modifier) -} -fun getEmptyComposableContentComponent() = EmptyComposableContentComponent + companion object { + val EMPTY = EmptyComposableContentComponent + } +} object EmptyComposableContentComponent : ComposableContentComponent { @Composable diff --git a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularContentComponent.kt b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularContentComponent.kt index 15a24d2001..f6531b2720 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularContentComponent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularContentComponent.kt @@ -15,9 +15,11 @@ interface ComposableModularContentComponent { @Composable fun Footer() -} -fun getEmptyComposableModularContentComponent() = EmptyComposableModularContentComponent + companion object { + val EMPTY = EmptyComposableModularContentComponent + } +} object EmptyComposableModularContentComponent : ComposableModularContentComponent { @Composable diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt index 696d1619ee..85baa41281 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -52,10 +52,10 @@ fun TangemTheme( @Composable fun TangemTheme( - isDark: Boolean = false, windowSize: WindowSize, typography: TangemTypography = TangemTheme.typography, dimens: TangemDimens = TangemTheme.dimens, + isDark: Boolean = false, vibratorHapticManager: VibratorHapticManager? = null, eventMessageHandler: EventMessageHandler = remember { EventMessageHandler() }, snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, diff --git a/core/ui/src/main/java/com/tangem/core/ui/security/DisableScreenshots.kt b/core/ui/src/main/java/com/tangem/core/ui/security/DisableScreenshots.kt index 58466d1140..dcb5b64ee2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/security/DisableScreenshots.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/security/DisableScreenshots.kt @@ -1,11 +1,53 @@ package com.tangem.core.ui.security +import android.app.Activity import android.view.WindowManager import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.platform.LocalContext import com.tangem.core.ui.utils.findActivity -import timber.log.Timber + +private val LocalSecureFlagController = staticCompositionLocalOf { + error("No SecureFlagController provided") +} + +private class SecureFlagController(private val activity: Activity) { + private var count by mutableIntStateOf(0) + + fun enable() { + if (count == 0) { + activity.window.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE, + ) + } + count++ + } + + fun disable() { + count-- + if (count == 0) { + activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + } +} + +@Composable +fun ProvideSecureFlagController(content: @Composable () -> Unit) { + val activity = LocalContext.current.findActivity() + val controller = remember(activity) { SecureFlagController(activity) } + + CompositionLocalProvider( + LocalSecureFlagController provides controller, + content = content, + ) +} /** * Disables screenshots for the current composition. @@ -14,18 +56,10 @@ import timber.log.Timber */ @Composable fun DisableScreenshotsDisposableEffect() { - val activity = LocalContext.current.findActivity() + val secureFlagController = LocalSecureFlagController.current - DisposableEffect(activity) { - Timber.d("Security mode: enabled") - activity.window.setFlags( - WindowManager.LayoutParams.FLAG_SECURE, - WindowManager.LayoutParams.FLAG_SECURE, - ) - - onDispose { - Timber.d("Security mode: disabled") - activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) - } + DisposableEffect(secureFlagController) { + secureFlagController.enable() + onDispose { secureFlagController.disable() } } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/BaseActionButtonsBlockTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/BaseActionButtonsBlockTestTags.kt new file mode 100644 index 0000000000..187a53a936 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/BaseActionButtonsBlockTestTags.kt @@ -0,0 +1,6 @@ +package com.tangem.core.ui.test + +object BaseActionButtonsBlockTestTags { + const val HORIZONTAL_ACTION_CHIPS = "BASE_ACTION_BUTTONS_BLOCK_HORIZONTAL_ACTION_CHIPS" + const val ACTION_BUTTON = "BASE_ACTION_BUTTONS_BLOCK_ACTION_BUTTON" +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/EmptyTransactionBlockTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/EmptyTransactionBlockTestTags.kt new file mode 100644 index 0000000000..2544f1f036 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/EmptyTransactionBlockTestTags.kt @@ -0,0 +1,8 @@ +package com.tangem.core.ui.test + +object EmptyTransactionBlockTestTags { + const val BLOCK = "EMPTY_TRANSACTION_BLOCK" + const val ICON = "EMPTY_TRANSACTION_BLOCK_ICON" + const val TEXT = "EMPTY_TRANSACTION_BLOCK_TEXT" + const val EXPLORE_BUTTON = "EMPTY_TRANSACTION_BLOCK_EXPLORE_BUTTON" +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt index cb1b1e1edf..269fc77316 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/MainScreenTestTags.kt @@ -7,9 +7,9 @@ object MainScreenTestTags { const val TOKEN_LIST_ITEM = "MAIN_SCREEN_TOKEN_LIST_ITEM" const val WALLET_LIST_ITEM = "MAIN_SCREEN_WALLET_LIST_ITEM" const val ORGANIZE_TOKENS_BUTTON = "MAIN_SCREEN_ORGANIZE_TOKENS_BUTTON" - const val MULTI_CURRENCY_ACTION_BUTTON = "MAIN_SCREEN_MULTI_CURRENCY_ACTION_BUTTON" const val CARD_TITLE = "MAIN_SCREEN_CARD_TITLE" const val CARD_IMAGE = "MAIN_SCREEN_CARD_IMAGE" + const val DEVICES_COUNT = "MAIN_SCREEN_DEVICES_COUNT" const val WALLET_BALANCE = "MAIN_SCREEN_WALLET_BALANCE" const val TOTAL_BALANCE_MENU_ITEM = "MAIN_SCREEN_TOTAL_BALANCE_MENU_ITEM" diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/StoriesScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/StoriesScreenTestTags.kt index a1b7576f50..12d500ece7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/StoriesScreenTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/StoriesScreenTestTags.kt @@ -6,4 +6,6 @@ object StoriesScreenTestTags { const val ORDER_BUTTON = "STORIES_SCREEN_ORDER_BUTTON" const val CREATE_NEW_WALLET_BUTTON = "STORIES_SCREEN_CREATE_NEW_WALLET_BUTTON" const val ADD_EXISTING_WALLET_BUTTON = "STORIES_SCREEN_ADD_EXISTING_WALLET_BUTTON" + const val TITLE = "STORIES_SCREEN_TITLE" + const val TEXT = "STORIES_SCREEN_TEXT" } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/TokenDetailsScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/TokenDetailsScreenTestTags.kt index bcbee0ae13..f9e953824d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/TokenDetailsScreenTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/TokenDetailsScreenTestTags.kt @@ -4,8 +4,6 @@ object TokenDetailsScreenTestTags { const val SCREEN_CONTAINER = "TOKEN_DETAILS_SCREEN_CONTAINER" const val TOKEN_TITLE = "TOKEN_DETAILS_SCREEN_TOKEN_TITLE" - const val ACTION_BUTTON = "TOKEN_DETAILS_SCREEN_ACTION_BUTTON" - const val HORIZONTAL_ACTION_CHIPS = "TOKEN_DETAILS_SCREEN_HORIZONTAL_ACTION_CHIPS" const val STAKING_BLOCK = "TOKEN_DETAILS_SCREEN_STAKING_BLOCK" const val STAKING_AVAILABLE_BLOCK = "TOKEN_DETAILS_SCREEN_STAKING_AVAILABLE_BLOCK" diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt index ebce7baf36..06d5fe6b63 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt @@ -64,9 +64,7 @@ fun DecimalFormat.getValidatedNumberWithFixedDecimals(text: String, decimals: In val beforeDecimal = filteredChars.substringBefore(decimalSeparator) val afterDecimal = filteredChars.substringAfter(decimalSeparator) decimals.getWithIntegerDecimals(beforeDecimal, decimalSeparator, afterDecimal) - } - // If there is no dot, just take all digits - else { + } else { // If there is no dot, just take all digits filteredChars } } @@ -87,9 +85,7 @@ fun DecimalFormat.formatWithThousands(text: String, decimals: Int): String { .reversed() val afterDecimal = localizedText.substringAfter(decimalSeparator) decimals.getWithIntegerDecimals(beforeDecimal, decimalSeparator, afterDecimal) - } - // If there is no dot, just take all digits - else { + } else { // If there is no dot, just take all digits localizedText.reversed() .chunked(TEXT_CHUNK_THOUSAND) .joinToString(thousandsSeparator.toString()) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt index 7873012994..7d1c7e7c2c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/InputNumberFormatter.kt @@ -44,9 +44,7 @@ class InputNumberFormatter( val beforeDecimal = filteredChars.substringBefore(decimalSeparator) val afterDecimal = filteredChars.substringAfter(decimalSeparator) beforeDecimal + decimalSeparator + afterDecimal.take(decimals) - } - // If there is no dot, just take all digits - else { + } else { // If there is no dot, just take all digits filteredChars } } @@ -62,9 +60,7 @@ class InputNumberFormatter( .reversed() val afterDecimal = text.substringAfter(decimalSeparator) beforeDecimal + decimalSeparator + afterDecimal.take(decimals) - } - // If there is no dot, just take all digits - else { + } else { // If there is no dot, just take all digits text.reversed() .chunked(TEXT_CHUNK_THOUSAND) .joinToString(thousandsSeparator.toString()) diff --git a/core/ui/src/main/res/drawable-hdpi/img_push_reminder.webp b/core/ui/src/main/res/drawable-hdpi/img_push_reminder.webp new file mode 100644 index 0000000000..47f81ba8f4 Binary files /dev/null and b/core/ui/src/main/res/drawable-hdpi/img_push_reminder.webp differ diff --git a/core/ui/src/main/res/drawable-mdpi/img_push_reminder.webp b/core/ui/src/main/res/drawable-mdpi/img_push_reminder.webp new file mode 100644 index 0000000000..1c1df20a6f Binary files /dev/null and b/core/ui/src/main/res/drawable-mdpi/img_push_reminder.webp differ diff --git a/core/ui/src/main/res/drawable-xhdpi/img_push_reminder.webp b/core/ui/src/main/res/drawable-xhdpi/img_push_reminder.webp new file mode 100644 index 0000000000..f30b2ac133 Binary files /dev/null and b/core/ui/src/main/res/drawable-xhdpi/img_push_reminder.webp differ diff --git a/core/ui/src/main/res/drawable-xxhdpi/img_push_reminder.webp b/core/ui/src/main/res/drawable-xxhdpi/img_push_reminder.webp new file mode 100644 index 0000000000..274b3305f6 Binary files /dev/null and b/core/ui/src/main/res/drawable-xxhdpi/img_push_reminder.webp differ diff --git a/core/ui/src/main/res/drawable-xxxhdpi/img_push_reminder.webp b/core/ui/src/main/res/drawable-xxxhdpi/img_push_reminder.webp new file mode 100644 index 0000000000..fe2d4ec006 Binary files /dev/null and b/core/ui/src/main/res/drawable-xxxhdpi/img_push_reminder.webp differ diff --git a/core/ui/src/main/res/drawable/ic_attention_12.xml b/core/ui/src/main/res/drawable/ic_attention_12.xml new file mode 100644 index 0000000000..960069e5dd --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_attention_12.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_notification_56.xml b/core/ui/src/main/res/drawable/ic_notification_56.xml new file mode 100644 index 0000000000..69a6c39afb --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_notification_56.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_notification_square_24.xml b/core/ui/src/main/res/drawable/ic_notification_square_24.xml new file mode 100644 index 0000000000..0a8e4b2db8 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_notification_square_24.xml @@ -0,0 +1,12 @@ + + + + diff --git a/core/ui/src/main/res/drawable/ic_promo_kyc_36.xml b/core/ui/src/main/res/drawable/ic_promo_kyc_36.xml new file mode 100644 index 0000000000..54962fbcae --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_promo_kyc_36.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/core/ui/src/main/res/drawable/ic_stars_24.xml b/core/ui/src/main/res/drawable/ic_stars_24.xml new file mode 100644 index 0000000000..41a21a8f25 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_stars_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_tangem_pay_promo_card_36.xml b/core/ui/src/main/res/drawable/ic_tangem_pay_promo_card_36.xml new file mode 100644 index 0000000000..4fc9a9cf1c --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_tangem_pay_promo_card_36.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/img_tangem_pay_card_main_36.xml b/core/ui/src/main/res/drawable/img_tangem_pay_card_main_36.xml new file mode 100644 index 0000000000..1b23c9f415 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_tangem_pay_card_main_36.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_visa_label_26_16.xml b/core/ui/src/main/res/drawable/img_visa_label_26_16.xml new file mode 100644 index 0000000000..c5859a2fda --- /dev/null +++ b/core/ui/src/main/res/drawable/img_visa_label_26_16.xml @@ -0,0 +1,17 @@ + + + + diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt b/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt index 6ab240ea41..2a9271dc93 100644 --- a/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt +++ b/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt @@ -20,7 +20,22 @@ fun Collection.copy(): Collection { return this.map { it } } -inline fun List.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? { - val index = indexOfFirst(predicate) - return if (index == -1) null else index +/** + * Adds an element to the mutable list if the specified condition is true. + * + * @param condition The condition to evaluate. + * @param create A lambda function that creates the element to be added. + */ +inline fun MutableCollection.addIf(condition: Boolean, create: () -> T) { + addIf(condition = condition, element = create()) +} + +/** + * Adds an element to the mutable list if the specified condition is true. + * + * @param condition The condition to evaluate. + * @param element The element to be added. + */ +fun MutableCollection.addIf(condition: Boolean, element: T) { + if (condition) this.add(element) } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/List.kt b/core/utils/src/main/java/com/tangem/utils/extensions/List.kt index cad9f1fada..a47b33dee3 100644 --- a/core/utils/src/main/java/com/tangem/utils/extensions/List.kt +++ b/core/utils/src/main/java/com/tangem/utils/extensions/List.kt @@ -72,4 +72,9 @@ fun List.filterIf(condition: Boolean, predicate: (T) -> Boolean): List } else { this } +} + +inline fun List.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? { + val index = indexOfFirst(predicate) + return if (index == -1) null else index } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/ETagsStore.kt b/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/ETagsStore.kt index 430e9a0290..bae6820a73 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/ETagsStore.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/cache/etag/ETagsStore.kt @@ -29,6 +29,5 @@ interface ETagsStore { enum class Key { WalletAccounts, UserTokens, - ; } } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/quote/QuotesFetcher.kt b/data/common/src/main/kotlin/com/tangem/data/common/quote/QuotesFetcher.kt index 4f1e8b92ce..bc89150de8 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/quote/QuotesFetcher.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/quote/QuotesFetcher.kt @@ -47,7 +47,6 @@ interface QuotesFetcher { value = setOf(PRICE, PRICE_CHANGE_24H, PRICE_CHANGE_1W, PRICE_CHANGE_30D).combine(), ), LAST_UPDATED_AT(value = "lastUpdatedAt"), - ; } sealed interface Error { diff --git a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt index d386676472..7a9b4c6cbe 100644 --- a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt +++ b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt @@ -2,11 +2,13 @@ package com.tangem.data.notifications import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.get import com.tangem.datasource.local.preferences.utils.getObjectMapSync import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.getSyncOrNull import com.tangem.datasource.local.preferences.utils.store import com.tangem.domain.notifications.repository.NotificationsRepository +import kotlinx.coroutines.flow.Flow import javax.inject.Inject class DefaultNotificationsRepository @Inject constructor( @@ -17,6 +19,10 @@ class DefaultNotificationsRepository @Inject constructor( return appPreferencesStore.getSyncOrDefault(PreferencesKeys.getShouldShowNotificationKey(key), true) } + override fun getShouldShowNotification(key: String): Flow { + return appPreferencesStore.get(PreferencesKeys.getShouldShowNotificationKey(key), true) + } + override suspend fun setShouldShowNotifications(key: String, value: Boolean) { appPreferencesStore.store(PreferencesKeys.getShouldShowNotificationKey(key), value) } diff --git a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt index 277a7125be..1c07c142a5 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt @@ -1,6 +1,8 @@ package com.tangem.data.swap -import arrow.core.right +import arrow.core.none +import arrow.core.some +import arrow.core.toOption import com.squareup.moshi.Moshi import com.tangem.data.common.api.safeApiCall import com.tangem.data.swap.converter.SwapDataConverter @@ -28,7 +30,7 @@ import com.tangem.domain.quotes.single.SingleQuoteStatusProducer import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier import com.tangem.domain.swap.SwapRepositoryV2 import com.tangem.domain.swap.models.* -import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator +import com.tangem.domain.tokens.operations.CryptoCurrencyStatusFactory import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async @@ -49,7 +51,6 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( private val dataSignatureVerifier: DataSignatureVerifier, private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier, private val singleQuoteStatusFetcher: SingleQuoteStatusFetcher, - private val currencyStatusProxyCreator: CurrencyStatusProxyCreator, @NetworkMoshi moshi: Moshi, ) : SwapRepositoryV2 { @@ -391,17 +392,19 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( ) } - return currencyStatusProxyCreator.createCurrencyStatus( + val quoteStatus = quote ?: singleQuoteStatusSupplier.getSyncOrNull( + params = SingleQuoteStatusProducer.Params(rawCurrencyId = rawCurrencyId), + ) + + return CryptoCurrencyStatusFactory.create( currency = cryptoCurrency, - maybeQuoteStatus = quote?.right() ?: singleQuoteStatusSupplier.getSyncOrNull( - params = SingleQuoteStatusProducer.Params(rawCurrencyId = rawCurrencyId), - ).right(), maybeNetworkStatus = NetworkStatus( network = cryptoCurrency.network, value = NetworkStatus.MissedDerivation, // Caution!!! Do not change this status - ).right(), - maybeYieldBalance = null, - ).getOrNull() + ).some(), + maybeQuoteStatus = quoteStatus.toOption(), + maybeYieldBalance = none(), + ) } private fun parseTxDetails(txDetailsJson: String): TxDetails? { diff --git a/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt b/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt index 5edf45f002..595d799f2d 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt @@ -17,7 +17,6 @@ import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier import com.tangem.domain.swap.SwapErrorResolver import com.tangem.domain.swap.SwapRepositoryV2 import com.tangem.domain.swap.SwapTransactionRepository -import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -59,7 +58,6 @@ internal object SwapDataModule { moshi = moshi, singleQuoteStatusSupplier = singleQuoteStatusSupplier, singleQuoteStatusFetcher = singleQuoteStatusFetcher, - currencyStatusProxyCreator = CurrencyStatusProxyCreator(), ) } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index fe5dd5cc34..23f10b2f4d 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -9,6 +9,7 @@ import com.tangem.data.tokens.repository.DefaultCurrenciesRepository import com.tangem.data.tokens.repository.DefaultCurrencyChecksRepository import com.tangem.data.tokens.repository.DefaultPolkadotAccountHealthCheckRepository import com.tangem.data.tokens.repository.DefaultTokenReceiveWarningsViewedRepository +import com.tangem.data.tokens.repository.DefaultYieldSupplyWarningsViewedRepository import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.datasource.local.preferences.AppPreferencesStore @@ -19,6 +20,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository +import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -96,4 +98,16 @@ internal object TokensDataModule { tokenReceiveWarningActionStore = tokenReceiveWarningActionStore, ) } + + @Provides + @Singleton + fun provideDefaultYieldSupplyWarningsViewedRepository( + appPreferencesStore: AppPreferencesStore, + dispatchers: CoroutineDispatcherProvider, + ): YieldSupplyWarningsViewedRepository { + return DefaultYieldSupplyWarningsViewedRepository( + appPreferencesStore = appPreferencesStore, + dispatchers = dispatchers, + ) + } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultYieldSupplyWarningsViewedRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultYieldSupplyWarningsViewedRepository.kt new file mode 100644 index 0000000000..193bbb03c6 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultYieldSupplyWarningsViewedRepository.kt @@ -0,0 +1,36 @@ +package com.tangem.data.tokens.repository + +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectSet +import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.withContext + +internal class DefaultYieldSupplyWarningsViewedRepository( + private val appPreferencesStore: AppPreferencesStore, + private val dispatchers: CoroutineDispatcherProvider, +) : YieldSupplyWarningsViewedRepository { + + override suspend fun getViewedWarnings(): Set = withContext(dispatchers.io) { + appPreferencesStore.getObjectSet(PreferencesKeys.YIELD_SUPPLY_WARNINGS_STATES_KEY).firstOrNull() + ?: emptySet() + } + + override suspend fun view(symbol: String) = withContext(dispatchers.io) { + appPreferencesStore.editData { mutablePreferences -> + val stored = mutablePreferences.getObjectSet( + PreferencesKeys.YIELD_SUPPLY_WARNINGS_STATES_KEY, + ) ?: mutableSetOf() + + val updated = stored + symbol + + mutablePreferences.setObjectSet( + key = PreferencesKeys.YIELD_SUPPLY_WARNINGS_STATES_KEY, + value = updated, + ) + } + return@withContext + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index c2d3ece8ad..a29a800638 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -1,11 +1,15 @@ package com.tangem.data.pay.di import com.tangem.data.pay.repository.DefaultKycRepository +import com.tangem.data.pay.repository.DefaultTangemPayTxHistoryRepository import com.tangem.data.pay.repository.DefaultOnboardingRepository import com.tangem.domain.pay.repository.KycRepository import com.tangem.domain.pay.repository.OnboardingRepository +import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase +import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository import dagger.Binds import dagger.Module +import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @@ -21,4 +25,18 @@ internal interface TangemPayDataModule { @Binds @Singleton fun bindOnboardingRepository(repository: DefaultOnboardingRepository): OnboardingRepository + + @Binds + @Singleton + fun bindTangemPayTxHistoryRepository(repository: DefaultTangemPayTxHistoryRepository): TangemPayTxHistoryRepository + + companion object { + @Provides + @Singleton + fun provideTangemPayMainScreenCustomerInfoUseCase( + repository: OnboardingRepository, + ): TangemPayMainScreenCustomerInfoUseCase { + return TangemPayMainScreenCustomerInfoUseCase(repository = repository) + } + } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultKycRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultKycRepository.kt index 0230e63d63..1775e41665 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultKycRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultKycRepository.kt @@ -1,6 +1,5 @@ package com.tangem.data.pay.repository -import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.domain.pay.KycStartInfo import com.tangem.domain.pay.repository.KycRepository @@ -16,9 +15,9 @@ internal class DefaultKycRepository @Inject constructor( override suspend fun getKycStartInfo() = withContext(dispatchers.io) { requestHelper.request { authHeader -> - tangemPayApi.getKycAccess(authHeader = authHeader).getOrThrow().result + tangemPayApi.getKycAccess(authHeader = authHeader) }.map { - KycStartInfo(token = it.token, locale = it.locale) + KycStartInfo(token = it.result.token, locale = it.result.locale) } } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt index 24eb2cbaa8..0307d16174 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt @@ -3,13 +3,13 @@ package com.tangem.data.pay.repository import arrow.core.Either import arrow.core.raise.either import com.tangem.core.error.UniversalError -import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.models.request.DeeplinkValidityRequest +import com.tangem.datasource.api.pay.models.response.CustomerMeResponse +import com.tangem.domain.pay.model.CustomerInfo.CardInfo import com.tangem.domain.pay.model.CustomerInfo -import com.tangem.domain.pay.model.ProductInstance +import com.tangem.domain.pay.model.CustomerInfo.ProductInstance import com.tangem.domain.pay.repository.OnboardingRepository -import com.tangem.domain.visa.error.VisaApiError import javax.inject.Inject private const val VALID_STATUS = "valid" @@ -21,20 +21,38 @@ internal class DefaultOnboardingRepository @Inject constructor( override suspend fun validateDeeplink(link: String): Either = either { return requestHelper.request { - tangemPayApi.validateDeeplink(DeeplinkValidityRequest(link)).getOrThrow().result - ?: raise(VisaApiError.UnknownWithoutCode) - }.map { result -> result.status == VALID_STATUS } + tangemPayApi.validateDeeplink(DeeplinkValidityRequest(link)) + }.map { it.result?.status == VALID_STATUS } } override suspend fun getCustomerInfo(): Either = either { return requestHelper.request { authHeader -> - val response = tangemPayApi.getCustomerMe(authHeader).getOrThrow() - response.result ?: raise(VisaApiError.UnknownWithoutCode) - }.map { result -> - CustomerInfo( - productInstance = result.productInstance?.let { ProductInstance(id = it.id, status = it.status) }, - kycStatus = result.kyc?.status, + tangemPayApi.getCustomerMe(authHeader) + }.map { getCustomerInfo(it.result) } + } + + override suspend fun getMainScreenCustomerInfo(): Either = either { + return requestHelper.requestWithPersistedToken { authHeader -> + tangemPayApi.getCustomerMe(authHeader) + }.map { getCustomerInfo(it.result) } + } + + private fun getCustomerInfo(response: CustomerMeResponse.Result?): CustomerInfo { + val card = response?.card + val balance = response?.balance + val cardInfo = if (card != null && balance != null) { + CardInfo( + lastFourDigits = card.cardNumberEnd, + balance = balance.availableBalance, + currencyCode = balance.currency, ) + } else { + null } + return CustomerInfo( + productInstance = response?.productInstance?.let { ProductInstance(id = it.id, status = it.status) }, + kycStatus = response?.kyc?.status, + cardInfo = cardInfo, + ) } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt new file mode 100644 index 0000000000..f104ae6725 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt @@ -0,0 +1,95 @@ +package com.tangem.data.pay.repository + +import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.visa.utils.TangemPayTxHistoryItemConverter +import com.tangem.datasource.api.pay.TangemPayApi +import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchFlow +import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchingContext +import com.tangem.domain.tangempay.model.TangemPayTxHistoryListConfig +import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository +import com.tangem.domain.visa.model.TangemPayTxHistoryItem +import com.tangem.pagination.BatchFetchResult +import com.tangem.pagination.BatchListSource +import com.tangem.pagination.fetcher.BatchFetcher +import com.tangem.pagination.fetcher.CursorBatchFetcher +import com.tangem.pagination.toBatchFlow +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import javax.inject.Inject + +private const val INITIAL_CURSOR = "initial_cursor_key" + +internal class DefaultTangemPayTxHistoryRepository @Inject constructor( + private val requestPerformer: TangemPayRequestPerformer, + private val visaApi: TangemPayApi, + private val cacheRegistry: CacheRegistry, + private val txHistoryItemsStore: TangemPayTxHistoryItemsStore, + private val dispatchers: CoroutineDispatcherProvider, +) : TangemPayTxHistoryRepository { + + override fun getTxHistoryBatchFlow( + batchSize: Int, + context: TangemPayTxHistoryListBatchingContext, + ): TangemPayTxHistoryListBatchFlow { + return BatchListSource( + fetchDispatcher = dispatchers.io, + context = context, + generateNewKey = { keys -> keys.lastOrNull()?.inc() ?: 0 }, + batchFetcher = createFetcher(batchSize), + ).toBatchFlow() + } + + private fun createFetcher( + batchSize: Int, + ): BatchFetcher> { + return CursorBatchFetcher( + prefetchDistance = batchSize, + batchSize = batchSize, + subFetcher = { request, _, _ -> + val items = loadItems(config = request.params, cursor = request.cursor, limit = request.limit) + BatchFetchResult.Success( + data = items, + last = items.size < request.limit, + empty = items.isEmpty(), + ) + }, + cursorFromItem = { item -> item.id }, // last item’s id becomes next cursor + ) + } + + private suspend fun loadItems( + config: TangemPayTxHistoryListConfig, + cursor: String?, + limit: Int, + ): List { + cacheRegistry.invokeOnExpire( + key = getCacheKey(userWalletId = config.userWalletId, cursor = cursor), + skipCache = config.refresh, + block = { fetch(userWalletId = config.userWalletId, cursor = cursor, pageSize = limit) }, + ) + + return txHistoryItemsStore.getSyncOrNull( + key = config.userWalletId, + cursor = cursor ?: INITIAL_CURSOR, + ).orEmpty() + } + + private fun getCacheKey(userWalletId: UserWalletId, cursor: String?): String { + return "tangem_pay_tx_history_${userWalletId}_${cursor ?: INITIAL_CURSOR}" + } + + private suspend fun fetch(userWalletId: UserWalletId, cursor: String?, pageSize: Int) { + val response = requestPerformer.request { authHeader -> + visaApi.getTangemPayTxHistory( + authHeader = authHeader, + limit = pageSize, + cursor = cursor, + ) + }.getOrNull() + response?.let { + val items = TangemPayTxHistoryItemConverter.convertList(response.result.transactions) + txHistoryItemsStore.store(key = userWalletId, cursor = cursor ?: INITIAL_CURSOR, value = items) + } + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt index 943480ef0e..ccd82930e5 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt @@ -4,22 +4,26 @@ import arrow.core.Either import arrow.core.raise.either import com.squareup.moshi.Moshi import com.tangem.core.error.UniversalError +import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.common.response.ApiResponseError +import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.pay.models.response.VisaErrorResponseJsonAdapter import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.visa.TangemPayStorage -import com.tangem.domain.core.wallets.UserWalletsListRepository import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.pay.datasource.TangemPayAuthDataSource import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.visa.error.VisaApiError import com.tangem.domain.visa.model.VisaAuthTokens +import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.utils.coroutines.CoroutineDispatcherProvider import javax.inject.Inject import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext @@ -33,9 +37,9 @@ internal class TangemPayRequestPerformer @Inject constructor( @NetworkMoshi moshi: Moshi, private val dispatchers: CoroutineDispatcherProvider, private val tangemPayStorage: TangemPayStorage, - private val userWalletsRepository: UserWalletsListRepository, private val getCurrencyUseCase: GetSingleCryptoCurrencyStatusUseCase, private val authDataSource: TangemPayAuthDataSource, + private val getWalletsUseCase: GetWalletsUseCase, ) { private val visaErrorAdapter = VisaErrorResponseJsonAdapter(moshi) @@ -44,24 +48,42 @@ internal class TangemPayRequestPerformer @Inject constructor( private val refreshTokensMutex = Mutex() private var refreshTokensJob: Deferred>? = null - suspend fun request(requestBlock: suspend (header: String) -> T): Either = either { + suspend fun request(requestBlock: suspend (header: String) -> ApiResponse): Either = + either { + withContext(dispatchers.io) { + performRequest( + requestBlock = requestBlock, + getTokens = ::getAccessTokens, + refreshTokens = ::refreshAuthTokens, + ).bind() + } + } + + suspend fun requestWithPersistedToken( + requestBlock: suspend (header: String) -> ApiResponse, + ): Either = either { withContext(dispatchers.io) { - performRequest(requestBlock = requestBlock, refreshTokens = ::refreshAuthTokens).bind() + performRequest( + requestBlock = requestBlock, + getTokens = ::getAccessTokensIfSaved, + refreshTokens = ::refreshAuthTokens, + ).bind() } } private suspend fun performRequest( - requestBlock: suspend (header: String) -> T, + requestBlock: suspend (header: String) -> ApiResponse, + getTokens: (suspend () -> Either), refreshTokens: (suspend () -> Either)? = null, ): Either = either { runCatching { - requestBlock("Bearer ${getAccessTokens().bind().accessToken}") + requestBlock("Bearer ${getTokens().bind().accessToken}").getOrThrow() }.getOrElse { error -> when (error) { is ApiResponseError.HttpException -> { if (refreshTokens != null && error.code == ApiResponseError.HttpException.Code.UNAUTHORIZED) { refreshOrJoin(refreshTokens).bind() - performRequest(requestBlock, refreshTokens = null).bind() + performRequest(requestBlock, refreshTokens = null, getTokens = getTokens).bind() } else { raise(mapHttpError(error)) } @@ -100,9 +122,7 @@ internal class TangemPayRequestPerformer @Inject constructor( } private suspend fun getCustomerWalletAddress(): Either = either { - customerWalletAddress - ?: tangemPayStorage.getCustomerWalletAddress() - ?: fetchAuthInputData().bind().address + customerWalletAddress ?: fetchAuthInputData().bind().address } private suspend fun getAccessTokens(): Either = either { @@ -110,6 +130,11 @@ internal class TangemPayRequestPerformer @Inject constructor( tangemPayStorage.getAuthTokens(address) ?: fetchTokens().bind() } + private suspend fun getAccessTokensIfSaved(): Either = either { + tangemPayStorage.getAuthTokens(getCustomerWalletAddress().bind()) + ?: raise(VisaApiError.UnknownWithoutCode) + } + private fun mapHttpError(throwable: ApiResponseError.HttpException): UniversalError { val errorBody = throwable.errorBody ?: return VisaApiError.UnknownWithoutCode return runCatching { @@ -122,14 +147,16 @@ internal class TangemPayRequestPerformer @Inject constructor( } private suspend fun fetchAuthInputData(): Either = either { - val wallet = userWalletsRepository.userWalletsSync().find { it is UserWallet.Cold } as? UserWallet.Cold + val userWallets = getWalletsUseCase() + .filter { it.isNotEmpty() } + .first() + val wallet = userWallets.find { it is UserWallet.Cold } as? UserWallet.Cold ?: raise(VisaApiError.UnknownWithoutCode) val address = getCurrencyUseCase.invokeMultiWalletSync(wallet.walletId, CryptoCurrency.ID.fromValue(POL_VALUE)) .getOrNull()?.value?.networkAddress?.defaultAddress?.value ?: raise(VisaApiError.UnknownWithoutCode) customerWalletAddress = address - tangemPayStorage.storeCustomerWalletAddress(address) AuthInputData(address, wallet.cardId) } diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemConverter.kt new file mode 100644 index 0000000000..9c4257091e --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemConverter.kt @@ -0,0 +1,51 @@ +package com.tangem.data.visa.utils + +import com.tangem.datasource.api.pay.models.response.TangemPayTxHistoryResponse +import com.tangem.domain.visa.model.TangemPayTxHistoryItem +import com.tangem.utils.converter.Converter + +internal object TangemPayTxHistoryItemConverter : + Converter { + + @Suppress("CyclomaticComplexMethod") + override fun convert(value: TangemPayTxHistoryResponse.Transaction): TangemPayTxHistoryItem { + val spend = value.spend + val collateral = value.collateral + val payment = value.payment + val fee = value.fee + + return TangemPayTxHistoryItem( + id = value.id, + date = when { + spend != null -> spend.postedAt + collateral != null -> collateral.postedAt + payment != null -> payment.postedAt + fee != null -> fee.postedAt + else -> null + }, + amount = when { + spend != null -> spend.amount + collateral != null -> collateral.amount + payment != null -> payment.amount + fee != null -> fee.amount + else -> null + }, + merchantName = when { + spend != null -> spend.merchantName + else -> null + }, + status = when { + spend != null -> spend.status + payment != null -> payment.status + else -> null + }, + currency = when { + spend != null -> spend.currency + collateral != null -> collateral.currency + payment != null -> payment.currency + fee != null -> fee.currency + else -> null + }, + ) + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemConverter.kt new file mode 100644 index 0000000000..6311b23e70 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemConverter.kt @@ -0,0 +1,20 @@ +package com.tangem.data.visa.utils + +import com.tangem.datasource.api.pay.models.response.VisaTxHistoryResponse +import com.tangem.domain.visa.model.VisaTxHistoryItem +import com.tangem.utils.converter.Converter + +internal object VisaTxHistoryItemConverter : Converter { + + override fun convert(value: VisaTxHistoryResponse.Transaction): VisaTxHistoryItem { + return VisaTxHistoryItem( + id = value.transactionId.toString(), + date = value.transactionDt, + amount = value.blockchainAmount, + fiatAmount = value.transactionAmount, + merchantName = value.merchantName, + status = value.transactionStatus, + fiatCurrency = findCurrencyByNumericCode(value.transactionCurrencyCode), + ) + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt deleted file mode 100644 index d7be726e36..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.data.visa.utils - -import com.tangem.datasource.api.pay.models.response.VisaTxHistoryResponse -import com.tangem.domain.visa.model.VisaTxHistoryItem - -internal class VisaTxHistoryItemFactory { - - fun create(transaction: VisaTxHistoryResponse.Transaction): VisaTxHistoryItem { - return VisaTxHistoryItem( - id = transaction.transactionId.toString(), - date = transaction.transactionDt, - amount = transaction.blockchainAmount, - fiatAmount = transaction.transactionAmount, - merchantName = transaction.merchantName, - status = transaction.transactionStatus, - fiatCurrency = findCurrencyByNumericCode(transaction.transactionCurrencyCode), - ) - } -} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt index 44649d0bf8..01950d27b7 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt @@ -20,8 +20,6 @@ internal class VisaTxHistoryPagingSource( val requestTxHistory: suspend (offset: Int, pageSize: Int) -> VisaTxHistoryResponse, ) : PagingSource() { - private val itemsFactory = VisaTxHistoryItemFactory() - private val cardPublicKey = params.cardPublicKey private val pageSize = params.pageSize private val isRefresh = params.isRefresh @@ -82,7 +80,7 @@ internal class VisaTxHistoryPagingSource( pagedItems.update { it.toMutableMap().apply { - this[offset] = response.transactions.map(itemsFactory::create) + this[offset] = response.transactions.map(VisaTxHistoryItemConverter::convert) } } } diff --git a/data/wallet-manager/src/test/java/com/tangem/data/walletmanager/UpdateWalletManagerResultFactoryTest.kt b/data/wallet-manager/src/test/java/com/tangem/data/walletmanager/UpdateWalletManagerResultFactoryTest.kt index 4c5c929262..94d6776409 100644 --- a/data/wallet-manager/src/test/java/com/tangem/data/walletmanager/UpdateWalletManagerResultFactoryTest.kt +++ b/data/wallet-manager/src/test/java/com/tangem/data/walletmanager/UpdateWalletManagerResultFactoryTest.kt @@ -250,7 +250,9 @@ internal class UpdateWalletManagerResultFactoryTest { ), ), currenciesAmounts = setOf( - UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(value = BigDecimal.ZERO), // default for demo + UpdateWalletManagerResult.CryptoCurrencyAmount.Coin( + value = BigDecimal.ZERO, + ), // default for demo ), currentTransactions = emptySet(), ), @@ -272,7 +274,9 @@ internal class UpdateWalletManagerResultFactoryTest { ), ), currenciesAmounts = setOf( - UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(value = BigDecimal.ONE), // used demo amount + UpdateWalletManagerResult.CryptoCurrencyAmount.Coin( + value = BigDecimal.ONE, + ), // used demo amount ), currentTransactions = emptySet(), ), diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt index d8edb5dc18..56a5f027a2 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt @@ -415,6 +415,6 @@ internal class DefaultWalletsRepository( else -> ActivatePromoCodeError.ActivationFailed } return@fold error.left() - },) + }) } } \ No newline at end of file diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt index f305beb722..d2e030bc54 100644 --- a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt +++ b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt @@ -114,62 +114,64 @@ class DefaultWalletsRepositoryTest { } @Test - fun `GIVEN API returns wallets WHEN getWalletsInfo THEN should return converted wallets and update cache if requested`() = runTest { - // GIVEN - val applicationId = "test_app_id" - val wallet1Id = "1234567890abcdef" - val wallet2Id = "fedcba0987654321" - val walletResponses = listOf( - WalletResponse( - id = wallet1Id, - notifyStatus = true, - ), - WalletResponse( - id = wallet2Id, - notifyStatus = false, - ), - ) - coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses) - coEvery { preferencesDataStore.updateData(any()) } returns mockk() + fun `GIVEN API returns wallets WHEN getWalletsInfo THEN should return converted wallets and update cache if requested`() = + runTest { + // GIVEN + val applicationId = "test_app_id" + val wallet1Id = "1234567890abcdef" + val wallet2Id = "fedcba0987654321" + val walletResponses = listOf( + WalletResponse( + id = wallet1Id, + notifyStatus = true, + ), + WalletResponse( + id = wallet2Id, + notifyStatus = false, + ), + ) + coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses) + coEvery { preferencesDataStore.updateData(any()) } returns mockk() - // WHEN - val result = repository.getWalletsInfo(applicationId, updateCache = true) + // WHEN + val result = repository.getWalletsInfo(applicationId, updateCache = true) - // THEN - assertThat(result).hasSize(2) - assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id) - assertThat(result[0].isNotificationsEnabled).isTrue() - assertThat(result[1].walletId.stringValue).isEqualTo(wallet2Id) - assertThat(result[1].isNotificationsEnabled).isFalse() + // THEN + assertThat(result).hasSize(2) + assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id) + assertThat(result[0].isNotificationsEnabled).isTrue() + assertThat(result[1].walletId.stringValue).isEqualTo(wallet2Id) + assertThat(result[1].isNotificationsEnabled).isFalse() - coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) } - coVerify(exactly = 2) { preferencesDataStore.updateData(any()) } - } + coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) } + coVerify(exactly = 2) { preferencesDataStore.updateData(any()) } + } @Test - fun `GIVEN API returns wallets WHEN getWalletsInfo with updateCache false THEN should return converted wallets without updating cache`() = runTest { - // GIVEN - val applicationId = "test_app_id" - val wallet1Id = "1234567890abcdef" - val walletResponses = listOf( - WalletResponse( - id = wallet1Id, - notifyStatus = true, - ), - ) - coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses) + fun `GIVEN API returns wallets WHEN getWalletsInfo with updateCache false THEN should return converted wallets without updating cache`() = + runTest { + // GIVEN + val applicationId = "test_app_id" + val wallet1Id = "1234567890abcdef" + val walletResponses = listOf( + WalletResponse( + id = wallet1Id, + notifyStatus = true, + ), + ) + coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses) - // WHEN - val result = repository.getWalletsInfo(applicationId, updateCache = false) + // WHEN + val result = repository.getWalletsInfo(applicationId, updateCache = false) - // THEN - assertThat(result).hasSize(1) - assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id) - assertThat(result[0].isNotificationsEnabled).isTrue() + // THEN + assertThat(result).hasSize(1) + assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id) + assertThat(result[0].isNotificationsEnabled).isTrue() - coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) } - coVerify(exactly = 0) { preferencesDataStore.updateData(any()) } - } + coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) } + coVerify(exactly = 0) { preferencesDataStore.updateData(any()) } + } @Test fun `GIVEN user wallets and application ID WHEN associateWallets THEN should convert and send to API`() = runTest { @@ -271,8 +273,8 @@ class DefaultWalletsRepositoryTest { // GIVEN coEvery { tangemTechApi.activatePromoCode(any()) } returns ApiResponse.Error( - HttpException(code = HttpException.Code.NOT_FOUND, message = null, errorBody = null), - ) as ApiResponse + HttpException(code = HttpException.Code.NOT_FOUND, message = null, errorBody = null), + ) as ApiResponse // WHEN val result = repository.activatePromoCode(promoCode = "PROMO", bitcoinAddress = "addr") @@ -288,8 +290,8 @@ class DefaultWalletsRepositoryTest { // GIVEN coEvery { tangemTechApi.activatePromoCode(any()) } returns ApiResponse.Error( - HttpException(code = HttpException.Code.CONFLICT, message = null, errorBody = null), - ) as ApiResponse + HttpException(code = HttpException.Code.CONFLICT, message = null, errorBody = null), + ) as ApiResponse // WHEN val result = repository.activatePromoCode(promoCode = "PROMO", bitcoinAddress = "addr") diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt index 86c802d7db..ea544fed18 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt @@ -12,7 +12,6 @@ import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.models.yield.supply.YieldSupplyStatus import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository @@ -30,6 +29,7 @@ internal class DefaultYieldSupplyTransactionRepository( override suspend fun createEnterTransactions( userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, + maxNetworkFee: BigDecimal, ): List { val cryptoCurrency = cryptoCurrencyStatus.currency @@ -51,27 +51,23 @@ internal class DefaultYieldSupplyTransactionRepository( cryptoCurrency = cryptoCurrency, ) ?: error("Calculated yield contract address is null") - val yieldTokenStatus = cryptoCurrencyStatus.value.yieldSupplyStatus ?: getYieldTokenStatus( - walletManager = walletManager, - cryptoCurrency = cryptoCurrency, - ) + val maxNetworkFee = maxNetworkFee.convertToSdkAmount(cryptoCurrencyStatus) return buildEnterTransactions( walletManager = walletManager, - cryptoCurrency = cryptoCurrency, + cryptoCurrencyStatus = cryptoCurrencyStatus, existingYieldContractAddress = existingYieldContractAddress, calculatedYieldContractAddress = calculatedYieldContractAddress, - yieldTokenStatus = yieldTokenStatus, + maxNetworkFee = maxNetworkFee, ) } override suspend fun createExitTransaction( userWalletId: UserWalletId, - cryptoCurrency: CryptoCurrency, - yieldSupplyStatus: YieldSupplyStatus, + cryptoCurrencyStatus: CryptoCurrencyStatus, fee: Fee?, ): TransactionData.Uncompiled = withContext(dispatchers.io) { - require(cryptoCurrency is CryptoCurrency.Token) + val cryptoCurrency = cryptoCurrencyStatus.currency as CryptoCurrency.Token val walletManager = walletManagersFacade.getOrCreateWalletManager( userWalletId = userWalletId, @@ -88,19 +84,24 @@ internal class DefaultYieldSupplyTransactionRepository( cryptoCurrency = cryptoCurrency, callData = callData, destinationAddress = walletManager.getYieldContract(), - yieldSupplyStatus = yieldSupplyStatus, + amount = BigDecimal.ZERO.convertToSdkAmount(cryptoCurrencyStatus), fee = fee, ) } + @Suppress("LongParameterList") private fun buildEnterTransactions( walletManager: WalletManager, - cryptoCurrency: CryptoCurrency.Token, + cryptoCurrencyStatus: CryptoCurrencyStatus, existingYieldContractAddress: String?, calculatedYieldContractAddress: String, - yieldTokenStatus: YieldSupplyStatus?, + maxNetworkFee: Amount, ): MutableList { val enterTransactions = mutableListOf() + val cryptoCurrency = cryptoCurrencyStatus.currency as CryptoCurrency.Token + val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus + + val amount = BigDecimal.ZERO.convertToSdkAmount(cryptoCurrencyStatus) when { existingYieldContractAddress == null || existingYieldContractAddress == EthereumUtils.ZERO_ADDRESS -> { @@ -108,30 +109,34 @@ internal class DefaultYieldSupplyTransactionRepository( createDeployTransaction( walletManager = walletManager, cryptoCurrency = cryptoCurrency, + amount = amount, + maxNetworkFee = maxNetworkFee, ), ) } - yieldTokenStatus == null -> error("Yield token status is null") - !yieldTokenStatus.isInitialized -> enterTransactions.add( + yieldSupplyStatus == null -> error("Yield token status is null") + !yieldSupplyStatus.isInitialized -> enterTransactions.add( createInitTokenTransaction( walletManager = walletManager, cryptoCurrency = cryptoCurrency, - yieldSupplyStatus = yieldTokenStatus, yieldContractAddress = calculatedYieldContractAddress, + amount = amount, + maxNetworkFee = maxNetworkFee, ), ) - !yieldTokenStatus.isActive -> enterTransactions.add( + !yieldSupplyStatus.isActive -> enterTransactions.add( createReactivateTokenTransaction( walletManager = walletManager, cryptoCurrency = cryptoCurrency, - yieldSupplyStatus = yieldTokenStatus, yieldContractAddress = calculatedYieldContractAddress, + amount = amount, + maxNetworkFee = maxNetworkFee, ), ) else -> Unit } - if (yieldTokenStatus?.isAllowedToSpend == false) { + if (yieldSupplyStatus?.isAllowedToSpend != true) { enterTransactions.add( createTransaction( walletManager = walletManager, @@ -141,7 +146,7 @@ internal class DefaultYieldSupplyTransactionRepository( amount = null, ), destinationAddress = cryptoCurrency.contractAddress, - yieldSupplyStatus = yieldTokenStatus, + amount = amount, fee = null, ), ) @@ -151,7 +156,7 @@ internal class DefaultYieldSupplyTransactionRepository( createEnterTransaction( walletManager = walletManager, cryptoCurrency = cryptoCurrency, - yieldSupplyStatus = yieldTokenStatus, + amount = amount, yieldContractAddress = calculatedYieldContractAddress, ), ) @@ -171,11 +176,10 @@ internal class DefaultYieldSupplyTransactionRepository( derivationPath = cryptoCurrency.network.derivationPath.value, ) ?: error("Wallet manager not found") walletManager.calculateYieldContract() - }.onFailure(Timber::e) - .getOrNull() + }.onFailure(Timber::e).getOrNull() } - private suspend fun getYieldContractAddress(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String? = + override suspend fun getYieldContractAddress(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String? = withContext(dispatchers.io) { require(cryptoCurrency is CryptoCurrency.Token) runCatching { @@ -185,35 +189,19 @@ internal class DefaultYieldSupplyTransactionRepository( derivationPath = cryptoCurrency.network.derivationPath.value, ) ?: error("Wallet manager not found") walletManager.getYieldContract() - }.onFailure(Timber::e) - .getOrNull() + }.onFailure(Timber::e).getOrNull() } - private suspend fun getYieldTokenStatus( - walletManager: WalletManager, - cryptoCurrency: CryptoCurrency, - ): YieldSupplyStatus? = withContext(dispatchers.io) { - require(cryptoCurrency is CryptoCurrency.Token) - runCatching { - val sdkSupplyStatus = walletManager.getYieldSupplyStatus(cryptoCurrency.contractAddress) - val isAllowedToSpend = walletManager.isAllowedToSpend(cryptoCurrency.contractAddress) - - YieldSupplyStatus( - isActive = sdkSupplyStatus?.isActive == true, - isInitialized = sdkSupplyStatus?.isInitialized == true, - isAllowedToSpend = isAllowedToSpend, - ) - }.onFailure(Timber::e).getOrNull() - } - private fun createDeployTransaction( walletManager: WalletManager, cryptoCurrency: CryptoCurrency.Token, + amount: Amount, + maxNetworkFee: Amount, ): TransactionData.Uncompiled { val callData = YieldSupplyContractCallDataProviderFactory.getDeployCallData( tokenContractAddress = cryptoCurrency.contractAddress, walletAddress = walletManager.wallet.address, - maxNetworkFee = MAX_NETWORK_FEE.convertToSdkAmount(cryptoCurrency), + maxNetworkFee = maxNetworkFee, ) val factoryContractAddress = walletManager.getYieldSupplyContractAddresses()?.factoryContractAddress @@ -224,7 +212,7 @@ internal class DefaultYieldSupplyTransactionRepository( cryptoCurrency = cryptoCurrency, callData = callData, destinationAddress = factoryContractAddress, - yieldSupplyStatus = null, + amount = amount, fee = null, ) } @@ -233,11 +221,12 @@ internal class DefaultYieldSupplyTransactionRepository( walletManager: WalletManager, cryptoCurrency: CryptoCurrency.Token, yieldContractAddress: String, - yieldSupplyStatus: YieldSupplyStatus, + amount: Amount, + maxNetworkFee: Amount, ): TransactionData.Uncompiled { val callData = YieldSupplyContractCallDataProviderFactory.getInitTokenCallData( tokenContractAddress = cryptoCurrency.contractAddress, - maxNetworkFee = MAX_NETWORK_FEE.convertToSdkAmount(cryptoCurrency), + maxNetworkFee = maxNetworkFee, ) return createTransaction( @@ -245,7 +234,7 @@ internal class DefaultYieldSupplyTransactionRepository( cryptoCurrency = cryptoCurrency, callData = callData, destinationAddress = yieldContractAddress, - yieldSupplyStatus = yieldSupplyStatus, + amount = amount, fee = null, ) } @@ -254,11 +243,12 @@ internal class DefaultYieldSupplyTransactionRepository( walletManager: WalletManager, cryptoCurrency: CryptoCurrency.Token, yieldContractAddress: String, - yieldSupplyStatus: YieldSupplyStatus, + amount: Amount, + maxNetworkFee: Amount, ): TransactionData.Uncompiled { val callData = YieldSupplyContractCallDataProviderFactory.getReactivateTokenCallData( tokenContractAddress = cryptoCurrency.contractAddress, - maxNetworkFee = MAX_NETWORK_FEE.convertToSdkAmount(cryptoCurrency), + maxNetworkFee = maxNetworkFee, ) return createTransaction( @@ -266,7 +256,7 @@ internal class DefaultYieldSupplyTransactionRepository( cryptoCurrency = cryptoCurrency, callData = callData, destinationAddress = yieldContractAddress, - yieldSupplyStatus = yieldSupplyStatus, + amount = amount, fee = null, ) } @@ -274,7 +264,7 @@ internal class DefaultYieldSupplyTransactionRepository( private fun createEnterTransaction( walletManager: WalletManager, cryptoCurrency: CryptoCurrency.Token, - yieldSupplyStatus: YieldSupplyStatus?, + amount: Amount, yieldContractAddress: String, ): TransactionData.Uncompiled { val callData = YieldSupplyContractCallDataProviderFactory.getEnterCallData( @@ -286,7 +276,7 @@ internal class DefaultYieldSupplyTransactionRepository( cryptoCurrency = cryptoCurrency, callData = callData, destinationAddress = yieldContractAddress, - yieldSupplyStatus = yieldSupplyStatus, + amount = amount, fee = null, ) } @@ -297,7 +287,7 @@ internal class DefaultYieldSupplyTransactionRepository( cryptoCurrency: CryptoCurrency, callData: SmartContractCallData, destinationAddress: String, - yieldSupplyStatus: YieldSupplyStatus?, + amount: Amount, fee: Fee?, ): TransactionData.Uncompiled { requireNotNull(cryptoCurrency as? CryptoCurrency.Token) @@ -308,8 +298,6 @@ internal class DefaultYieldSupplyTransactionRepository( blockchain = blockchain, ) - val amount = getYieldSupplyAmount(cryptoCurrency, yieldSupplyStatus) - return if (fee != null) { walletManager.createTransaction( amount = amount, @@ -349,23 +337,4 @@ internal class DefaultYieldSupplyTransactionRepository( else -> error("Data extras not supported for $blockchain") } } - - private fun getYieldSupplyAmount(cryptoCurrency: CryptoCurrency.Token, yieldSupplyStatus: YieldSupplyStatus?) = - BigDecimal.ZERO.convertToSdkAmount( - cryptoCurrency = cryptoCurrency, - amountType = AmountType.TokenYieldSupply( - token = Token( - symbol = cryptoCurrency.symbol, - contractAddress = cryptoCurrency.contractAddress, - decimals = cryptoCurrency.decimals, - ), - isActive = yieldSupplyStatus?.isActive ?: false, - isInitialized = yieldSupplyStatus?.isInitialized ?: false, - isAllowedToSpend = yieldSupplyStatus?.isAllowedToSpend ?: false, - ), - ) - - private companion object { - val MAX_NETWORK_FEE: BigDecimal = BigDecimal.TEN // TODO for TESTNET only - } } \ No newline at end of file diff --git a/data/yield-supply/src/test/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepositoryTest.kt b/data/yield-supply/src/test/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepositoryTest.kt index 069e3630f8..7421ab8399 100644 --- a/data/yield-supply/src/test/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepositoryTest.kt +++ b/data/yield-supply/src/test/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepositoryTest.kt @@ -16,7 +16,6 @@ import com.tangem.domain.models.yield.supply.YieldSupplyStatus import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import com.tangem.blockchain.yieldsupply.providers.YieldSupplyStatus as SDKYieldSupplyStatus import io.mockk.coEvery import io.mockk.every import io.mockk.mockk @@ -26,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import java.math.BigDecimal +import com.tangem.blockchain.yieldsupply.providers.YieldSupplyStatus as SDKYieldSupplyStatus @TestInstance(TestInstance.Lifecycle.PER_CLASS) class DefaultYieldSupplyTransactionRepositoryTest { @@ -74,7 +74,11 @@ class DefaultYieldSupplyTransactionRepositoryTest { coEvery { walletManager.isAllowedToSpend(any()) } returns false coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress - val result = repository.createEnterTransactions(userWalletId, cryptoCurrencyStatus) + val result = repository.createEnterTransactions( + userWalletId = userWalletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + maxNetworkFee = BigDecimal.TEN, + ) // Assert that 3 transactions are returned: deploy, approve, enter Truth.assertThat(result).isNotNull() @@ -84,7 +88,7 @@ class DefaultYieldSupplyTransactionRepositoryTest { val firstExpectedCallData = YieldSupplyContractCallDataProviderFactory.getDeployCallData( walletAddress = walletManager.wallet.address, tokenContractAddress = mockedContractAddress, - maxNetworkFee = BigDecimal.TEN.convertToSdkAmount(cryptoCurrency), + maxNetworkFee = BigDecimal.TEN.convertToSdkAmount(cryptoCurrencyStatus), ) val firstTransaction = result.first() @@ -117,13 +121,18 @@ class DefaultYieldSupplyTransactionRepositoryTest { fun `createEnterTransactions returns init-approve-enter transactions`() = runTest { coEvery { walletManager.getYieldContract() } returns yieldContractAddress coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress + coEvery { walletManager.isAllowedToSpend(any()) } returns false coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus( isActive = false, isInitialized = false, maxNetworkFee = BigDecimal.TEN, ) - val result = repository.createEnterTransactions(userWalletId, cryptoCurrencyStatus) + val result = repository.createEnterTransactions( + userWalletId = userWalletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + maxNetworkFee = BigDecimal.TEN, + ) // Assert that 3 transactions are returned: init token, approve, enter Truth.assertThat(result).isNotNull() @@ -132,7 +141,7 @@ class DefaultYieldSupplyTransactionRepositoryTest { // Check transaction - init token val firstExpectedCallData = YieldSupplyContractCallDataProviderFactory.getInitTokenCallData( tokenContractAddress = mockedContractAddress, - maxNetworkFee = BigDecimal.TEN.convertToSdkAmount(cryptoCurrency), + maxNetworkFee = BigDecimal.TEN.convertToSdkAmount(cryptoCurrencyStatus), ) val firstTransaction = result.first() @@ -165,13 +174,18 @@ class DefaultYieldSupplyTransactionRepositoryTest { fun `createEnterTransactions returns reactivate-approve-enter transactions`() = runTest { coEvery { walletManager.getYieldContract() } returns yieldContractAddress coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress + coEvery { walletManager.isAllowedToSpend(any()) } returns false coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus( isActive = false, isInitialized = true, maxNetworkFee = BigDecimal.TEN, ) - val result = repository.createEnterTransactions(userWalletId, cryptoCurrencyStatus) + val result = repository.createEnterTransactions( + userWalletId = userWalletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + maxNetworkFee = BigDecimal.TEN, + ) // Assert that 3 transactions are returned: reactivate token, approve, enter Truth.assertThat(result).isNotNull() @@ -180,7 +194,7 @@ class DefaultYieldSupplyTransactionRepositoryTest { // Check transaction - reactivate token val firstExpectedCallData = YieldSupplyContractCallDataProviderFactory.getReactivateTokenCallData( tokenContractAddress = mockedContractAddress, - maxNetworkFee = BigDecimal.TEN.convertToSdkAmount(cryptoCurrency), + maxNetworkFee = BigDecimal.TEN.convertToSdkAmount(cryptoCurrencyStatus), ) val firstTransaction = result.first() @@ -220,7 +234,11 @@ class DefaultYieldSupplyTransactionRepositoryTest { ) coEvery { walletManager.isAllowedToSpend(any()) } returns true - val result = repository.createEnterTransactions(userWalletId, cryptoCurrencyStatus) + val result = repository.createEnterTransactions( + userWalletId = userWalletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + maxNetworkFee = BigDecimal.TEN, + ) // Assert that 2 transactions are returned: approve, enter Truth.assertThat(result).isNotNull() @@ -229,7 +247,7 @@ class DefaultYieldSupplyTransactionRepositoryTest { // Check transaction - reactivate token val firstExpectedCallData = YieldSupplyContractCallDataProviderFactory.getReactivateTokenCallData( tokenContractAddress = mockedContractAddress, - maxNetworkFee = BigDecimal.TEN.convertToSdkAmount(cryptoCurrency), + maxNetworkFee = BigDecimal.TEN.convertToSdkAmount(cryptoCurrencyStatus), ) val firstTransaction = result.first() @@ -257,7 +275,11 @@ class DefaultYieldSupplyTransactionRepositoryTest { ) coEvery { walletManager.isAllowedToSpend(any()) } returns true - val result = repository.createEnterTransactions(userWalletId, cryptoCurrencyStatus) + val result = repository.createEnterTransactions( + userWalletId = userWalletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + maxNetworkFee = BigDecimal.TEN, + ) // Assert that transaction is returned: enter Truth.assertThat(result).isNotNull() diff --git a/domain/account/src/main/java/com/tangem/domain/account/models/AccountStatusList.kt b/domain/account/src/main/java/com/tangem/domain/account/models/AccountStatusList.kt index acd8f76d35..357cbf110c 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/models/AccountStatusList.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/models/AccountStatusList.kt @@ -1,5 +1,6 @@ package com.tangem.domain.account.models +import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.wallet.UserWallet import kotlinx.serialization.Serializable @@ -18,4 +19,13 @@ data class AccountStatusList( val userWallet: UserWallet, val accountStatuses: Set, val totalAccounts: Int, -) \ No newline at end of file + val totalFiatBalance: TotalFiatBalance = TotalFiatBalance.Failed, +) { + + val mainAccount: AccountStatus + get() = accountStatuses.first { accountStatus -> + when (accountStatus) { + is AccountStatus.CryptoPortfolio -> accountStatus.account.isMainAccount + } + } +} \ No newline at end of file diff --git a/domain/account/src/main/java/com/tangem/domain/account/producer/SingleAccountListProducer.kt b/domain/account/src/main/java/com/tangem/domain/account/producer/SingleAccountListProducer.kt index 8d85130007..9acea3c434 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/producer/SingleAccountListProducer.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/producer/SingleAccountListProducer.kt @@ -5,7 +5,7 @@ import com.tangem.domain.core.flow.FlowProducer import com.tangem.domain.models.wallet.UserWalletId /** - * Produces a list of [AccountList] for a specific user wallet. + * Produces a [AccountList] for a specific user wallet. * [REDACTED_AUTHOR] */ diff --git a/domain/account/status/.gitignore b/domain/account/status/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/domain/account/status/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/account/status/build.gradle.kts b/domain/account/status/build.gradle.kts new file mode 100644 index 0000000000..104086e62c --- /dev/null +++ b/domain/account/status/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.domain.account.status" +} + +tasks.withType().configureEach { + useJUnitPlatform() +} + +dependencies { + implementation(projects.domain.account) + implementation(projects.domain.core) + implementation(projects.domain.quotes) + implementation(projects.domain.models) + implementation(projects.domain.networks) + implementation(projects.domain.staking) + implementation(projects.domain.tokens) + + implementation(projects.libs.crypto) + + implementation(deps.kotlin.datetime) + + // region DI + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) + // end + + testImplementation(deps.test.coroutine) + testImplementation(deps.test.junit5) + testRuntimeOnly(deps.test.junit5.engine) + testImplementation(deps.test.mockk) + testImplementation(deps.test.truth) + testImplementation(tangemDeps.blockchain) + testImplementation(projects.common.test) +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusListProducerFactoryModule.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusListProducerFactoryModule.kt new file mode 100644 index 0000000000..926780dc55 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusListProducerFactoryModule.kt @@ -0,0 +1,28 @@ +package com.tangem.domain.account.status.di + +import com.tangem.domain.account.status.producer.DefaultMultiAccountStatusListProducer +import com.tangem.domain.account.status.producer.DefaultSingleAccountStatusListProducer +import com.tangem.domain.account.status.producer.MultiAccountStatusListProducer +import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface AccountStatusListProducerFactoryModule { + + @Binds + @Singleton + fun bindSingleAccountStatusListProducerFactory( + factory: DefaultSingleAccountStatusListProducer.Factory, + ): SingleAccountStatusListProducer.Factory + + @Binds + @Singleton + fun bindMultiAccountStatusListProducerFactory( + factory: DefaultMultiAccountStatusListProducer.Factory, + ): MultiAccountStatusListProducer.Factory +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusListSupplierModule.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusListSupplierModule.kt new file mode 100644 index 0000000000..51b10dc806 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusListSupplierModule.kt @@ -0,0 +1,38 @@ +package com.tangem.domain.account.status.di + +import com.tangem.domain.account.status.producer.MultiAccountStatusListProducer +import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer +import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier +import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object AccountStatusListSupplierModule { + + @Provides + @Singleton + fun provideSingleAccountStatusListSupplier( + factory: SingleAccountStatusListProducer.Factory, + ): SingleAccountStatusListSupplier { + return object : SingleAccountStatusListSupplier( + factory = factory, + keyCreator = { "account_status_list_${it.userWalletId}" }, + ) {} + } + + @Provides + @Singleton + fun provideMultiAccountStatusListSupplier( + factory: MultiAccountStatusListProducer.Factory, + ): MultiAccountStatusListSupplier { + return object : MultiAccountStatusListSupplier( + factory = factory, + keyCreator = { "multi_account_status_list" }, + ) {} + } +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultMultiAccountStatusListProducer.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultMultiAccountStatusListProducer.kt new file mode 100644 index 0000000000..8f4335ee9d --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultMultiAccountStatusListProducer.kt @@ -0,0 +1,48 @@ +package com.tangem.domain.account.status.producer + +import arrow.core.Option +import arrow.core.some +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier +import com.tangem.domain.core.wallets.UserWalletsListRepository +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest + +/** +[REDACTED_AUTHOR] + */ +// TODO: Finalize [REDACTED_JIRA] +internal class DefaultMultiAccountStatusListProducer @AssistedInject constructor( + @Assisted val params: Unit, + private val userWalletsListRepository: UserWalletsListRepository, + private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier, +) : MultiAccountStatusListProducer { + + override val fallback: Option> = emptyList().some() + + @OptIn(ExperimentalCoroutinesApi::class) + override fun produce(): Flow> { + return userWalletsListRepository.userWallets + .filterNotNull() + .flatMapLatest { userWallets -> + val flows = userWallets.map { + singleAccountStatusListSupplier( + params = SingleAccountStatusListProducer.Params(it.walletId), + ) + } + + combine(flows) { it.toList() } + } + } + + @AssistedFactory + interface Factory : MultiAccountStatusListProducer.Factory { + override fun create(params: Unit): DefaultMultiAccountStatusListProducer + } +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt new file mode 100644 index 0000000000..ebb05f18cb --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt @@ -0,0 +1,50 @@ +package com.tangem.domain.account.status.producer + +import arrow.core.Option +import arrow.core.none +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.account.supplier.SingleAccountListSupplier +import com.tangem.domain.models.network.NetworkStatus +import com.tangem.domain.models.quote.QuoteStatus +import com.tangem.domain.models.staking.YieldBalance +import com.tangem.domain.networks.single.SingleNetworkStatusSupplier +import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier +import com.tangem.domain.staking.StakingIdFactory +import com.tangem.domain.staking.single.SingleYieldBalanceSupplier +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +/** +[REDACTED_AUTHOR] + */ +// TODO: Implement [REDACTED_JIRA] +@Suppress("UnusedPrivateProperty", "UnusedPrivateClass") +@OptIn(ExperimentalCoroutinesApi::class) +internal class DefaultSingleAccountStatusListProducer @AssistedInject constructor( + @Assisted private val params: SingleAccountStatusListProducer.Params, + private val singleAccountListSupplier: SingleAccountListSupplier, + private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier, + private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier, + private val singleYieldBalanceSupplier: SingleYieldBalanceSupplier, + private val stakingIdFactory: StakingIdFactory, +) : SingleAccountStatusListProducer { + + override val fallback: Option = none() + + override fun produce(): Flow = emptyFlow() + + private data class CryptoCurrencyStatusSources( + val networkStatus: NetworkStatus, + val yieldBalance: YieldBalance?, + val quoteStatus: QuoteStatus?, + ) + + @AssistedFactory + interface Factory : SingleAccountStatusListProducer.Factory { + override fun create(params: SingleAccountStatusListProducer.Params): DefaultSingleAccountStatusListProducer + } +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/MultiAccountStatusListProducer.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/MultiAccountStatusListProducer.kt new file mode 100644 index 0000000000..5ebd910a58 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/MultiAccountStatusListProducer.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.account.status.producer + +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.core.flow.FlowProducer + +/** + * Produces a list of [AccountList]s for all user wallets. + * +[REDACTED_AUTHOR] + */ +interface MultiAccountStatusListProducer : FlowProducer> { + + interface Factory : FlowProducer.Factory +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/SingleAccountStatusListProducer.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/SingleAccountStatusListProducer.kt new file mode 100644 index 0000000000..97935a14f3 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/SingleAccountStatusListProducer.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.account.status.producer + +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.core.flow.FlowProducer +import com.tangem.domain.models.wallet.UserWalletId + +/** + * Produces a [AccountStatusList] for a specific user wallet. + * +[REDACTED_AUTHOR] + */ +interface SingleAccountStatusListProducer : FlowProducer { + + data class Params(val userWalletId: UserWalletId) + + interface Factory : FlowProducer.Factory +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/supplier/MultiAccountStatusListSupplier.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/supplier/MultiAccountStatusListSupplier.kt new file mode 100644 index 0000000000..60ed1d5971 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/supplier/MultiAccountStatusListSupplier.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.account.status.supplier + +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.account.status.producer.MultiAccountStatusListProducer +import com.tangem.domain.core.flow.FlowCachingSupplier + +/** + * Supplier that provides a list of [AccountStatusList]s for all user wallets. + * +[REDACTED_AUTHOR] + */ +abstract class MultiAccountStatusListSupplier( + override val factory: MultiAccountStatusListProducer.Factory, + override val keyCreator: (Unit) -> String, +) : FlowCachingSupplier>() \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/supplier/SingleAccountStatusListSupplier.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/supplier/SingleAccountStatusListSupplier.kt new file mode 100644 index 0000000000..5e5ef8f506 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/supplier/SingleAccountStatusListSupplier.kt @@ -0,0 +1,15 @@ +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 + +/** + * Supplier that provides a single [AccountStatusList] for a specific user wallet. + * +[REDACTED_AUTHOR] + */ +abstract class SingleAccountStatusListSupplier( + override val factory: SingleAccountStatusListProducer.Factory, + override val keyCreator: (SingleAccountStatusListProducer.Params) -> String, +) : FlowCachingSupplier() \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/StateDialog.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/StateDialog.kt index 120604ae39..9d5084361d 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/StateDialog.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/redux/StateDialog.kt @@ -7,6 +7,6 @@ interface StateDialog { data class ScanFailsDialog(val source: ScanFailsSource, val onTryAgain: (() -> Unit)? = null) : StateDialog enum class ScanFailsSource { - MAIN, SIGN_IN, SETTINGS, INTRO; + MAIN, SIGN_IN, SETTINGS, INTRO } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/utils/BigDecimalUtils.kt b/domain/legacy/src/main/java/com/tangem/domain/utils/BigDecimalUtils.kt index 677c7b354a..f99fb60e4f 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/utils/BigDecimalUtils.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/utils/BigDecimalUtils.kt @@ -3,30 +3,37 @@ package com.tangem.domain.utils import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Token import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus import java.math.BigDecimal import com.tangem.blockchain.common.Amount as SdkAmount -/** Converts `BigDecimal` [cryptoCurrency] to [SdkAmount] */ -fun BigDecimal.convertToSdkAmount( - cryptoCurrency: CryptoCurrency, - amountType: AmountType = getAmountTypeFromCryptoCurrency(cryptoCurrency), -): SdkAmount = SdkAmount( - currencySymbol = cryptoCurrency.symbol, - value = this, - decimals = cryptoCurrency.decimals, - type = amountType, -) - -/** - * Converts [CryptoCurrency] to [AmountType] based on its type - */ -private fun getAmountTypeFromCryptoCurrency(cryptoCurrency: CryptoCurrency) = when (cryptoCurrency) { - is CryptoCurrency.Coin -> AmountType.Coin - is CryptoCurrency.Token -> AmountType.Token( - token = Token( - symbol = cryptoCurrency.symbol, - contractAddress = cryptoCurrency.contractAddress, - decimals = cryptoCurrency.decimals, - ), +/** Converts `BigDecimal` [cryptoCurrencyStatus] to [SdkAmount] */ +fun BigDecimal.convertToSdkAmount(cryptoCurrencyStatus: CryptoCurrencyStatus): SdkAmount { + val cryptoCurrency = cryptoCurrencyStatus.currency + val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus + return SdkAmount( + currencySymbol = cryptoCurrency.symbol, + value = this, + decimals = cryptoCurrency.decimals, + type = when (cryptoCurrency) { + is CryptoCurrency.Coin -> AmountType.Coin + is CryptoCurrency.Token -> { + val token = Token( + symbol = cryptoCurrency.symbol, + contractAddress = cryptoCurrency.contractAddress, + decimals = cryptoCurrency.decimals, + ) + if (yieldSupplyStatus == null) { + AmountType.Token(token = token) + } else { + AmountType.TokenYieldSupply( + token = token, + isActive = yieldSupplyStatus.isActive, + isInitialized = yieldSupplyStatus.isInitialized, + isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend, + ) + } + } + }, ) } \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/TokenReceiveConfig.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/TokenReceiveConfig.kt index d884942aeb..032940c3ee 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/TokenReceiveConfig.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/TokenReceiveConfig.kt @@ -29,6 +29,7 @@ data class ReceiveAddressModel( data class TokenReceiveNotification( val title: Int, val subtitle: Int, + val isYieldSupplyNotification: Boolean = false, ) enum class Asset { diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/TokensGroupType.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/TokensGroupType.kt index 23982e6f41..735b112edf 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/TokensGroupType.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/TokensGroupType.kt @@ -15,5 +15,4 @@ enum class TokensGroupType { /** Grouping by network */ NETWORK, - ; } \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/TokensSortType.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/TokensSortType.kt index 0e85408187..3a68f0be35 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/TokensSortType.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/TokensSortType.kt @@ -15,5 +15,4 @@ enum class TokensSortType { /** Sorted by their balance */ BALANCE, - ; } \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/TotalFiatBalance.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/TotalFiatBalance.kt index 374956a548..88af8c6bbd 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/TotalFiatBalance.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/TotalFiatBalance.kt @@ -26,13 +26,12 @@ sealed interface TotalFiatBalance { /** * Represents the successfully loaded state of the fiat balance. * - * @property amount the loaded fiat balance amount - * @property isAllAmountsSummarized indicates whether the amount includes a summary of all underlying amounts + * @property amount the loaded fiat balance amount + * @property source status source */ @Serializable data class Loaded( val amount: SerializedBigDecimal, - val isAllAmountsSummarized: Boolean, val source: StatusSource, ) : TotalFiatBalance } \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/account/CryptoPortfolioIcon.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/account/CryptoPortfolioIcon.kt index 15b906020b..681a6d940e 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/account/CryptoPortfolioIcon.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/account/CryptoPortfolioIcon.kt @@ -44,7 +44,6 @@ data class CryptoPortfolioIcon private constructor( Clock, Package, Gift, - ; } /** @@ -64,7 +63,6 @@ data class CryptoPortfolioIcon private constructor( Pattypan, UFOGreen, VitalGreen, - ; } companion object { diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/tokenlist/TokenList.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/tokenlist/TokenList.kt index bec343c4df..776c46147f 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/tokenlist/TokenList.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/tokenlist/TokenList.kt @@ -70,7 +70,6 @@ sealed interface TokenList { override val totalFiatBalance: TotalFiatBalance = TotalFiatBalance.Loaded( amount = SerializedBigDecimal.ZERO, - isAllAmountsSummarized = true, source = StatusSource.ACTUAL, ) diff --git a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt index 0f1d3cb606..00d20b0f04 100644 --- a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt +++ b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt @@ -1,5 +1,7 @@ package com.tangem.domain.notifications.repository +import kotlinx.coroutines.flow.Flow + /** * Repository interface for managing local notification logic and state. * @@ -20,6 +22,13 @@ interface NotificationsRepository { */ suspend fun shouldShowNotification(key: String): Boolean + /** + * Subscribe on whether a notification with the given [key] should be shown to the user. + * @param key The unique identifier for the notification. + * @return true if the notification should be shown, false otherwise. + */ + fun getShouldShowNotification(key: String): Flow + /** * Sets whether a notification with the given [key] should be shown to the user. * @param key The unique identifier for the notification. diff --git a/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt b/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt index e3a191f205..83fae860be 100644 --- a/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt +++ b/domain/notifications/src/test/java/com/tangem/domain/notifications/GetApplicationIdUseCaseTest.kt @@ -114,39 +114,40 @@ class GetApplicationIdUseCaseTest { } @Test - fun `GIVEN no local application ID WHEN multiple concurrent invokes with delay THEN create only one application ID`() = runTest { - // GIVEN - val newApplicationId = ApplicationId("new-app-id") - var isIdCreated = false + fun `GIVEN no local application ID WHEN multiple concurrent invokes with delay THEN create only one application ID`() = + runTest { + // GIVEN + val newApplicationId = ApplicationId("new-app-id") + var isIdCreated = false - coEvery { pushNotificationsRepository.getApplicationId() } answers { - if (!isIdCreated) null else newApplicationId - } - coEvery { pushNotificationsRepository.createApplicationId() } answers { - isIdCreated = true - newApplicationId - } - coEvery { pushNotificationsRepository.saveApplicationId(newApplicationId) } returns Unit + coEvery { pushNotificationsRepository.getApplicationId() } answers { + if (!isIdCreated) null else newApplicationId + } + coEvery { pushNotificationsRepository.createApplicationId() } answers { + isIdCreated = true + newApplicationId + } + coEvery { pushNotificationsRepository.saveApplicationId(newApplicationId) } returns Unit - // WHEN - val results = coroutineScope { - List(PARALLEL_COUNT) { - async { - delay(100) - useCase() - } - }.awaitAll() - } + // WHEN + val results = coroutineScope { + List(PARALLEL_COUNT) { + async { + delay(100) + useCase() + } + }.awaitAll() + } - // THEN - results.forEach { result -> - assertThat(result).isInstanceOf(Either.Right::class.java) - assertThat((result as Either.Right).value).isEqualTo(newApplicationId) + // THEN + results.forEach { result -> + assertThat(result).isInstanceOf(Either.Right::class.java) + assertThat((result as Either.Right).value).isEqualTo(newApplicationId) + } + coVerify(exactly = PARALLEL_COUNT + 1) { pushNotificationsRepository.getApplicationId() } + coVerify(exactly = 1) { pushNotificationsRepository.createApplicationId() } + coVerify(exactly = 1) { pushNotificationsRepository.saveApplicationId(newApplicationId) } } - coVerify(exactly = PARALLEL_COUNT + 1) { pushNotificationsRepository.getApplicationId() } - coVerify(exactly = 1) { pushNotificationsRepository.createApplicationId() } - coVerify(exactly = 1) { pushNotificationsRepository.saveApplicationId(newApplicationId) } - } companion object { private const val PARALLEL_COUNT = 100 diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/details/TokenAction.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/details/TokenAction.kt new file mode 100644 index 0000000000..5c134f0308 --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/details/TokenAction.kt @@ -0,0 +1,7 @@ +package com.tangem.domain.tokens.model.details + +enum class TokenAction { + Receive, + Send, + Swap, +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt index 0fc94390be..18401f9621 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt @@ -13,7 +13,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations -import com.tangem.domain.tokens.operations.TokenListFiatBalanceOperations +import com.tangem.domain.tokens.operations.TotalFiatBalanceCalculator import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import timber.log.Timber @@ -112,11 +112,8 @@ class GetWalletTotalBalanceUseCase( } } - val operations = TokenListFiatBalanceOperations( - currencies = ensureNotNull(statuses.toNonEmptyListOrNull()) { lceLoading() }, - isAnyTokenLoading = false, + TotalFiatBalanceCalculator.calculate( + statuses = ensureNotNull(statuses.toNonEmptyListOrNull()) { lceLoading() }, ) - - operations.calculateFiatBalance() } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCase.kt new file mode 100644 index 0000000000..417e84e987 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCase.kt @@ -0,0 +1,19 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +class NeedShowYieldSupplyDepositedWarningUseCase( + private val yieldSupplyWarningsViewedRepository: YieldSupplyWarningsViewedRepository, + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke(cryptoCurrencyStatus: CryptoCurrencyStatus?): Boolean = withContext(dispatchers.io) { + val hasActiveLending = cryptoCurrencyStatus?.value?.yieldSupplyStatus?.isActive == true + if (!hasActiveLending) return@withContext false + val showedWarnings = yieldSupplyWarningsViewedRepository.getViewedWarnings() + return@withContext !showedWarnings.contains(cryptoCurrencyStatus?.currency?.name) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/SaveViewedYieldSupplyWarningUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/SaveViewedYieldSupplyWarningUseCase.kt new file mode 100644 index 0000000000..8175e13b51 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/SaveViewedYieldSupplyWarningUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository + +class SaveViewedYieldSupplyWarningUseCase( + private val yieldSupplyWarningsViewedRepository: YieldSupplyWarningsViewedRepository, +) { + suspend operator fun invoke(symbol: String) { + yieldSupplyWarningsViewedRepository.view(symbol) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt index b0b9ac0e7c..11bdea2a34 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt @@ -4,12 +4,10 @@ import arrow.core.Either import arrow.core.raise.Raise import arrow.core.raise.either import arrow.core.raise.ensure -import arrow.core.raise.withError import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.tokens.error.TokenListSortingError -import com.tangem.domain.tokens.error.mapper.mapToTokenListSortingError -import com.tangem.domain.tokens.operations.TokenListSortingOperations +import com.tangem.domain.tokens.operations.TokenListFactory import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext @@ -30,36 +28,24 @@ class ToggleTokenListGroupingUseCase( } private fun Raise.groupTokens(tokenList: TokenList.Ungrouped): TokenList.GroupedByNetwork { - ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) { - TokenListSortingError.TokenListIsLoading - } + validate(tokenList) - val sortingOperations = TokenListSortingOperations(tokenList) - - return TokenList.GroupedByNetwork( - groups = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) { - sortingOperations.getGroupedTokens().bind() - }, - totalFiatBalance = tokenList.totalFiatBalance, - sortedBy = sortingOperations.getSortType(), - ) + return TokenListFactory.createGroupedByNetwork(tokenList) } - private fun Raise.ungroupTokens( - tokenList: TokenList.GroupedByNetwork, - ): TokenList.Ungrouped { + private fun Raise.ungroupTokens(tokenList: TokenList.GroupedByNetwork): TokenList.Ungrouped { + validate(tokenList) + + return TokenListFactory.createUngrouped(tokenList) + } + + private fun Raise.validate(tokenList: TokenList) { ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) { TokenListSortingError.TokenListIsLoading } - val sortingOperations = TokenListSortingOperations(tokenList) - - return TokenList.Ungrouped( - currencies = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) { - sortingOperations.getTokens().bind() - }, - totalFiatBalance = tokenList.totalFiatBalance, - sortedBy = sortingOperations.getSortType(), - ) + ensure(tokenList.flattenCurrencies().isNotEmpty()) { + TokenListSortingError.TokenListIsEmpty + } } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt index 9283f50615..2bc37f5bb1 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt @@ -1,16 +1,14 @@ package com.tangem.domain.tokens import arrow.core.Either -import arrow.core.raise.Raise import arrow.core.raise.either import arrow.core.raise.ensure -import arrow.core.raise.withError +import com.tangem.domain.models.TokensGroupType import com.tangem.domain.models.TokensSortType import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.tokens.error.TokenListSortingError -import com.tangem.domain.tokens.error.mapper.mapToTokenListSortingError -import com.tangem.domain.tokens.operations.TokenListSortingOperations +import com.tangem.domain.tokens.operations.TokenListFactory import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext @@ -21,53 +19,20 @@ class ToggleTokenListSortingUseCase( suspend operator fun invoke(tokenList: TokenList): Either { return withContext(dispatchers.default) { either { - when (tokenList) { - is TokenList.GroupedByNetwork -> sortGroupedTokenList(tokenList) - is TokenList.Ungrouped -> sortUngroupedTokenList(tokenList) - is TokenList.Empty -> raise(TokenListSortingError.TokenListIsEmpty) + ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) { + TokenListSortingError.TokenListIsLoading } + + TokenListFactory.create( + statuses = tokenList.flattenCurrencies(), + groupType = when (tokenList) { + is TokenList.GroupedByNetwork -> TokensGroupType.NETWORK + is TokenList.Ungrouped -> TokensGroupType.NONE + is TokenList.Empty -> raise(TokenListSortingError.TokenListIsEmpty) + }, + sortType = TokensSortType.BALANCE, + ) } } } - - private fun Raise.sortGroupedTokenList( - tokenList: TokenList.GroupedByNetwork, - ): TokenList.GroupedByNetwork { - ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) { - TokenListSortingError.TokenListIsLoading - } - - val operations = getSortingOperations(tokenList) - - return tokenList.copy( - groups = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) { - operations.getGroupedTokens().bind() - }, - sortedBy = operations.getSortType(), - ) - } - - private fun Raise.sortUngroupedTokenList( - tokenList: TokenList.Ungrouped, - ): TokenList.Ungrouped { - ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) { - TokenListSortingError.TokenListIsLoading - } - - val operations = getSortingOperations(tokenList) - - return tokenList.copy( - currencies = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) { - operations.getTokens().bind() - }, - sortedBy = operations.getSortType(), - ) - } - - private fun getSortingOperations(tokenList: TokenList): TokenListSortingOperations { - return TokenListSortingOperations( - tokenList = tokenList, - sortByBalance = tokenList.sortedBy != TokensSortType.BALANCE, - ) - } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt deleted file mode 100644 index eefa5d1417..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.domain.tokens.error.mapper - -import com.tangem.domain.tokens.error.TokenListSortingError -import com.tangem.domain.tokens.operations.TokenListSortingOperations - -internal fun TokenListSortingOperations.Error.mapToTokenListSortingError(): TokenListSortingError { - return when (this) { - is TokenListSortingOperations.Error.EmptyTokens -> TokenListSortingError.TokenListIsEmpty - is TokenListSortingOperations.Error.NetworkNotFound, - -> TokenListSortingError.UnableToSortTokenList - } -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt index 55d53e54c8..76c726d9de 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt @@ -54,7 +54,7 @@ abstract class BaseCurrencyStatusOperations( private val stakingIdFactory: StakingIdFactory, ) { - protected val currencyStatusProxyCreator = CurrencyStatusProxyCreator() + private val currencyStatusProxyCreator = CurrencyStatusProxyCreator() abstract fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt index d261a865d9..fb20d04a34 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt @@ -171,29 +171,22 @@ class CachedCurrenciesStatusesOperations( ): Lce> = lce { isLoading.set(isUpdating) - var quotesRetrievingFailed = false - val networksStatuses = maybeNetworkStatuses?.bindEither()?.toNonEmptySetOrNull() val yieldBalances = maybeYieldBalances?.bindEither() val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) { null } - if (quotes == null) { - quotesRetrievingFailed = true - } - currencies.map { currency -> val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId } val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network } val yieldBalance = findYieldBalanceOrNull(yieldBalances, currency, networkStatus) - val currencyStatus = currencyStatusProxyCreator.createCurrencyStatus( + val currencyStatus = CryptoCurrencyStatusFactory.create( currency = currency, - quoteStatus = quote, - networkStatus = networkStatus, - yieldBalance = yieldBalance, - ignoreQuote = quotesRetrievingFailed, + maybeNetworkStatus = networkStatus.toOption(), + maybeQuoteStatus = quote.toOption(), + maybeYieldBalance = yieldBalance.toOption(), ) currencyStatus diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt new file mode 100644 index 0000000000..3ba3d65d4e --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt @@ -0,0 +1,266 @@ +package com.tangem.domain.tokens.operations + +import arrow.core.Option +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.network.NetworkAddress +import com.tangem.domain.models.network.NetworkStatus +import com.tangem.domain.models.network.TxInfo +import com.tangem.domain.models.quote.QuoteStatus +import com.tangem.domain.models.staking.YieldBalance +import com.tangem.domain.models.yield.supply.YieldSupplyStatus +import java.math.BigDecimal + +/** + * Factory to create [CryptoCurrencyStatus] from [NetworkStatus], [QuoteStatus] and [YieldBalance]. + * +[REDACTED_AUTHOR] + */ +object CryptoCurrencyStatusFactory { + + private val QuoteStatus?.fiatRate: BigDecimal? + get() = (this?.value as? QuoteStatus.Data)?.fiatRate + + private val QuoteStatus?.priceChange: BigDecimal? + get() = (this?.value as? QuoteStatus.Data)?.priceChange + + /** + * Creates [CryptoCurrencyStatus] from [NetworkStatus], [QuoteStatus] and [YieldBalance]. + * + + * @param maybeNetworkStatus An optional network status containing blockchain information. + * @param maybeQuoteStatus An optional quote status containing price information. + * @param maybeYieldBalance An optional yield balance containing staking information. + */ + fun create( + currency: CryptoCurrency, + maybeNetworkStatus: Option, + maybeQuoteStatus: Option, + maybeYieldBalance: Option, + ): CryptoCurrencyStatus { + return CryptoCurrencyStatus( + currency = currency, + value = createStatus( + currency = currency, + maybeNetworkStatus = maybeNetworkStatus, + maybeYieldBalance = maybeYieldBalance, + maybeQuoteStatus = maybeQuoteStatus, + ), + ) + } + + private fun createStatus( + currency: CryptoCurrency, + maybeNetworkStatus: Option, + maybeQuoteStatus: Option, + maybeYieldBalance: Option, + ): CryptoCurrencyStatus.Value { + val quoteStatus = maybeQuoteStatus.getOrNull() + + return when (val status = maybeNetworkStatus.getOrNull()?.value) { + is NetworkStatus.MissedDerivation -> createMissedDerivation(quoteStatus) + is NetworkStatus.Unreachable -> createUnreachable(status, quoteStatus) + is NetworkStatus.NoAccount -> createNoAccount(status, quoteStatus) + is NetworkStatus.Verified -> createStatus(currency, status, quoteStatus, maybeYieldBalance) + null -> CryptoCurrencyStatus.Loading + } + } + + private fun createMissedDerivation(quoteStatus: QuoteStatus?): CryptoCurrencyStatus.MissedDerivation { + return CryptoCurrencyStatus.MissedDerivation( + priceChange = quoteStatus.priceChange, + fiatRate = quoteStatus.fiatRate, + ) + } + + private fun createUnreachable( + status: NetworkStatus.Unreachable, + quoteStatus: QuoteStatus?, + ): CryptoCurrencyStatus.Unreachable { + return CryptoCurrencyStatus.Unreachable( + priceChange = quoteStatus.priceChange, + fiatRate = quoteStatus.fiatRate, + networkAddress = status.address, + ) + } + + private fun createNoAccount( + status: NetworkStatus.NoAccount, + quoteStatus: QuoteStatus?, + ): CryptoCurrencyStatus.NoAccount { + return CryptoCurrencyStatus.NoAccount( + amountToCreateAccount = status.amountToCreateAccount, + fiatAmount = BigDecimal.ZERO, + priceChange = quoteStatus.priceChange, + fiatRate = quoteStatus.fiatRate, + networkAddress = status.address, + sources = CryptoCurrencyStatus.Sources( + networkSource = status.source, + quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, + ), + ) + } + + private fun createStatus( + currency: CryptoCurrency, + status: NetworkStatus.Verified, + quoteStatus: QuoteStatus?, + maybeYieldBalance: Option, + ): CryptoCurrencyStatus.Value { + val amount = when (val amount = status.amounts[currency.id]) { + is NetworkStatus.Amount.Loaded -> amount.value + is NetworkStatus.Amount.NotFound -> { + return createNoAmount(quoteStatus = quoteStatus) + } + null -> { + return CryptoCurrencyStatus.Loading + } + } + + val yieldBalance = maybeYieldBalance.getOrNull(id = currency.id, address = status.address) + + if (currency is CryptoCurrency.Token && currency.isCustom) { + return createCustom( + id = currency.id, + status = status, + amount = amount, + quoteStatus = quoteStatus, + yieldBalance = yieldBalance, + ) + } + + // order is important for correct total balance calculation + return when (val quoteValue = quoteStatus?.value) { + is QuoteStatus.Empty -> { + createNoQuote( + id = currency.id, + status = status, + amount = amount, + quoteStatus = quoteStatus, + yieldBalance = yieldBalance, + ) + } + is QuoteStatus.Data -> { + createLoaded( + id = currency.id, + status = status, + amount = amount, + quoteStatus = quoteValue, + yieldBalance = yieldBalance, + ) + } + null -> CryptoCurrencyStatus.Loading + } + } + + private fun createNoAmount(quoteStatus: QuoteStatus?): CryptoCurrencyStatus.NoAmount { + return CryptoCurrencyStatus.NoAmount( + priceChange = quoteStatus.priceChange, + fiatRate = quoteStatus.fiatRate, + ) + } + + private fun createCustom( + id: CryptoCurrency.ID, + status: NetworkStatus.Verified, + amount: BigDecimal, + quoteStatus: QuoteStatus?, + yieldBalance: YieldBalance.Data?, + ): CryptoCurrencyStatus.Custom { + return CryptoCurrencyStatus.Custom( + amount = amount, + fiatAmount = quoteStatus.fiatRate?.let { calculateFiatAmount(amount, it) }, + fiatRate = quoteStatus.fiatRate, + priceChange = quoteStatus.priceChange, + hasCurrentNetworkTransactions = status.hasCurrentNetworkTransactions(), + pendingTransactions = status.getCurrentTransactions(id), + networkAddress = status.address, + yieldBalance = yieldBalance, + yieldSupplyStatus = status.getYieldSupplyStatus(id), + sources = CryptoCurrencyStatus.Sources( + networkSource = status.source, + yieldBalanceSource = yieldBalance?.source ?: StatusSource.ACTUAL, + quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, + ), + ) + } + + private fun createNoQuote( + id: CryptoCurrency.ID, + status: NetworkStatus.Verified, + amount: BigDecimal, + quoteStatus: QuoteStatus?, + yieldBalance: YieldBalance.Data?, + ): CryptoCurrencyStatus.NoQuote { + return CryptoCurrencyStatus.NoQuote( + amount = amount, + hasCurrentNetworkTransactions = status.hasCurrentNetworkTransactions(), + pendingTransactions = status.getCurrentTransactions(id), + networkAddress = status.address, + yieldBalance = yieldBalance, + yieldSupplyStatus = status.getYieldSupplyStatus(id), + sources = CryptoCurrencyStatus.Sources( + networkSource = status.source, + yieldBalanceSource = yieldBalance?.source ?: StatusSource.ACTUAL, + quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, + ), + ) + } + + private fun createLoaded( + id: CryptoCurrency.ID, + status: NetworkStatus.Verified, + amount: BigDecimal, + quoteStatus: QuoteStatus.Data, + yieldBalance: YieldBalance.Data?, + ): CryptoCurrencyStatus.Loaded { + return CryptoCurrencyStatus.Loaded( + amount = amount, + fiatAmount = calculateFiatAmount(amount, quoteStatus.fiatRate), + fiatRate = quoteStatus.fiatRate, + priceChange = quoteStatus.priceChange, + hasCurrentNetworkTransactions = status.hasCurrentNetworkTransactions(), + pendingTransactions = status.getCurrentTransactions(id), + networkAddress = status.address, + yieldBalance = yieldBalance, + yieldSupplyStatus = status.getYieldSupplyStatus(id), + sources = CryptoCurrencyStatus.Sources( + networkSource = status.source, + yieldBalanceSource = yieldBalance?.source ?: StatusSource.ACTUAL, + quoteSource = quoteStatus.source, + ), + ) + } + + private fun NetworkStatus.Verified.hasCurrentNetworkTransactions() = pendingTransactions.isNotEmpty() + + private fun NetworkStatus.Verified.getCurrentTransactions(id: CryptoCurrency.ID): Set { + return pendingTransactions.getOrElse(key = id, defaultValue = ::emptySet) + } + + private fun NetworkStatus.Verified.getYieldSupplyStatus(id: CryptoCurrency.ID): YieldSupplyStatus? { + return yieldSupplyStatuses[id] + } + + private fun Option.getOrNull(id: CryptoCurrency.ID, address: NetworkAddress): YieldBalance.Data? { + val yieldBalance = this.getOrNull() as? YieldBalance.Data ?: return null + + val isCurrentAddressStaking = yieldBalance.stakingId.address == address.defaultAddress.value + val filteredTokenBalances = yieldBalance.balance.items.filter { + it.token.coinGeckoId == id.rawCurrencyId?.value + } + + return if (isCurrentAddressStaking && filteredTokenBalances.isNotEmpty()) { + yieldBalance.copy( + balance = yieldBalance.balance.copy(items = filteredTokenBalances), + ) + } else { + null + } + } + + private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal { + return amount * fiatRate + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt deleted file mode 100644 index 5a6576e4b3..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ /dev/null @@ -1,161 +0,0 @@ -package com.tangem.domain.tokens.operations - -import com.tangem.domain.models.StatusSource -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.network.NetworkStatus -import com.tangem.domain.models.quote.QuoteStatus -import com.tangem.domain.models.staking.YieldBalance -import java.math.BigDecimal - -internal class CurrencyStatusOperations( - private val currency: CryptoCurrency, - private val quoteStatus: QuoteStatus?, - private val networkStatus: NetworkStatus?, - private val yieldBalance: YieldBalance?, - private val ignoreQuote: Boolean, -) { - - private val QuoteStatus?.fiatRate: BigDecimal? - get() = (this?.value as? QuoteStatus.Data)?.fiatRate - - private val QuoteStatus?.priceChange: BigDecimal? - get() = (this?.value as? QuoteStatus.Data)?.priceChange - - fun createTokenStatus(): CryptoCurrencyStatus = CryptoCurrencyStatus(currency, createStatus()) - - private fun createStatus(): CryptoCurrencyStatus.Value { - return when (val status = networkStatus?.value) { - null -> CryptoCurrencyStatus.Loading - is NetworkStatus.MissedDerivation -> createMissedDerivationStatus() - is NetworkStatus.Unreachable -> createUnreachableStatus(status) - is NetworkStatus.NoAccount -> createNoAccountStatus(status) - is NetworkStatus.Verified -> createStatus(status, yieldBalance) - } - } - - private fun createMissedDerivationStatus(): CryptoCurrencyStatus.MissedDerivation = - CryptoCurrencyStatus.MissedDerivation(priceChange = quoteStatus?.priceChange, fiatRate = quoteStatus?.fiatRate) - - private fun createUnreachableStatus(status: NetworkStatus.Unreachable): CryptoCurrencyStatus.Unreachable { - return CryptoCurrencyStatus.Unreachable( - priceChange = quoteStatus?.priceChange, - fiatRate = quoteStatus?.fiatRate, - networkAddress = status.address, - ) - } - - private fun createNoAccountStatus(status: NetworkStatus.NoAccount): CryptoCurrencyStatus.NoAccount { - return CryptoCurrencyStatus.NoAccount( - amountToCreateAccount = status.amountToCreateAccount, - fiatAmount = if (quoteStatus == null) null else BigDecimal.ZERO, - priceChange = quoteStatus?.priceChange, - fiatRate = quoteStatus?.fiatRate, - networkAddress = status.address, - sources = CryptoCurrencyStatus.Sources( - networkSource = status.source, - quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, - ), - ) - } - - @Suppress("CyclomaticComplexMethod", "LongMethod") - private fun createStatus( - networkStatusValue: NetworkStatus.Verified, - yieldBalance: YieldBalance?, - ): CryptoCurrencyStatus.Value { - val amount = when (val amount = networkStatusValue.amounts[currency.id]) { - null -> { - return CryptoCurrencyStatus.Loading - } - is NetworkStatus.Amount.NotFound -> { - return CryptoCurrencyStatus.NoAmount( - priceChange = quoteStatus?.priceChange, - fiatRate = quoteStatus?.fiatRate, - ) - } - is NetworkStatus.Amount.Loaded -> amount.value - } - - val hasCurrentNetworkTransactions = networkStatusValue.pendingTransactions.isNotEmpty() - val currentTransactions = networkStatusValue.pendingTransactions.getOrElse(currency.id, ::emptySet) - val yieldBalanceData = yieldBalance as? YieldBalance.Data - val isCurrentAddressStaking = - yieldBalanceData?.stakingId?.address == networkStatusValue.address.defaultAddress.value - val filteredTokenBalances = yieldBalanceData?.balance?.items?.filter { - it.token.coinGeckoId == currency.id.rawCurrencyId?.value - } - val currentYieldBalance = if (isCurrentAddressStaking && filteredTokenBalances?.isNotEmpty() == true) { - yieldBalanceData.copy( - balance = yieldBalanceData.balance.copy( - items = filteredTokenBalances, - ), - ) - } else { - null - } - val yieldSupplyStatus = networkStatusValue.yieldSupplyStatuses[currency.id] - - val quoteValue = quoteStatus?.value - - // order is important for correct total balance calculation - return when { - currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom( - amount = amount, - fiatAmount = calculateFiatAmountOrNull(amount, quoteStatus?.fiatRate), - fiatRate = quoteStatus?.fiatRate, - priceChange = quoteStatus?.priceChange, - hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, - pendingTransactions = currentTransactions, - networkAddress = networkStatusValue.address, - yieldBalance = currentYieldBalance, - yieldSupplyStatus = yieldSupplyStatus, - sources = CryptoCurrencyStatus.Sources( - networkSource = networkStatusValue.source, - quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, - yieldBalanceSource = currentYieldBalance?.source ?: StatusSource.ACTUAL, - ), - ) - quoteValue is QuoteStatus.Empty || ignoreQuote -> CryptoCurrencyStatus.NoQuote( - amount = amount, - hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, - pendingTransactions = currentTransactions, - networkAddress = networkStatusValue.address, - yieldBalance = currentYieldBalance, - yieldSupplyStatus = yieldSupplyStatus, - sources = CryptoCurrencyStatus.Sources( - networkSource = networkStatusValue.source, - quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, - yieldBalanceSource = currentYieldBalance?.source ?: StatusSource.ACTUAL, - ), - ) - quoteValue is QuoteStatus.Data -> CryptoCurrencyStatus.Loaded( - amount = amount, - fiatAmount = calculateFiatAmount(amount, quoteValue.fiatRate), - fiatRate = quoteValue.fiatRate, - priceChange = quoteValue.priceChange, - hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, - pendingTransactions = currentTransactions, - networkAddress = networkStatusValue.address, - yieldBalance = currentYieldBalance, - yieldSupplyStatus = yieldSupplyStatus, - sources = CryptoCurrencyStatus.Sources( - networkSource = networkStatusValue.source, - quoteSource = quoteValue.source, - yieldBalanceSource = currentYieldBalance?.source ?: StatusSource.ACTUAL, - ), - ) - else -> CryptoCurrencyStatus.Loading - } - } - - private fun calculateFiatAmountOrNull(amount: BigDecimal, fiatRate: BigDecimal?): BigDecimal? { - if (fiatRate == null) return null - - return calculateFiatAmount(amount, fiatRate) - } - - private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal { - return amount * fiatRate - } -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFactory.kt new file mode 100644 index 0000000000..7a84e34972 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFactory.kt @@ -0,0 +1,132 @@ +package com.tangem.domain.tokens.operations + +import arrow.core.NonEmptyList +import arrow.core.toNonEmptyListOrNull +import com.tangem.domain.models.TokensGroupType +import com.tangem.domain.models.TokensSortType +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.staking.YieldBalance +import com.tangem.domain.models.tokenlist.TokenList +import com.tangem.domain.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup +import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance +import com.tangem.utils.extensions.orZero +import java.math.BigDecimal + +/** + * This factory creates a [TokenList] based on the provided list of [CryptoCurrencyStatus], [TokensGroupType], + * and [TokensSortType]. + * +[REDACTED_AUTHOR] + */ +object TokenListFactory { + + /** + * Creates a [TokenList] based on the provided parameters. + * + * @param statuses A list of [CryptoCurrencyStatus] representing the cryptocurrencies to be included in the token list. + * @param groupType The type of grouping to be applied to the token list, either by network or none. + * @param sortType The type of sorting to be applied to the token list, either by balance or none. + */ + fun create(statuses: List, groupType: TokensGroupType, sortType: TokensSortType): TokenList { + val nonEmptyStatuses = statuses.toNonEmptyListOrNull() ?: return TokenList.Empty + val totalFiatBalance = TotalFiatBalanceCalculator.calculate(statuses = nonEmptyStatuses) + + return when (groupType) { + TokensGroupType.NONE -> createUngrouped(totalFiatBalance, nonEmptyStatuses, sortType) + TokensGroupType.NETWORK -> createGroupedByNetwork(totalFiatBalance, nonEmptyStatuses, sortType) + } + } + + fun createUngrouped(tokenList: TokenList.GroupedByNetwork): TokenList.Ungrouped { + return createUngrouped( + totalFiatBalance = tokenList.totalFiatBalance, + nonEmptyStatuses = tokenList.flattenCurrencies().toNonEmptyListOrNull() + ?: error("GroupedByNetwork.flattenCurrencies should be non empty list"), + sortType = tokenList.sortedBy, + ) + } + + fun createGroupedByNetwork(tokenList: TokenList.Ungrouped): TokenList.GroupedByNetwork { + return createGroupedByNetwork( + totalFiatBalance = tokenList.totalFiatBalance, + nonEmptyStatuses = tokenList.flattenCurrencies().toNonEmptyListOrNull() + ?: error("Ungrouped.flattenCurrencies should be non empty list"), + sortType = tokenList.sortedBy, + ) + } + + private fun createUngrouped( + totalFiatBalance: TotalFiatBalance, + nonEmptyStatuses: NonEmptyList, + sortType: TokensSortType, + ): TokenList.Ungrouped { + return TokenList.Ungrouped( + totalFiatBalance = totalFiatBalance, + sortedBy = sortType, + currencies = nonEmptyStatuses.sortStatuses(sortType), + ) + } + + private fun createGroupedByNetwork( + totalFiatBalance: TotalFiatBalance, + nonEmptyStatuses: NonEmptyList, + sortType: TokensSortType, + ): TokenList.GroupedByNetwork { + return TokenList.GroupedByNetwork( + totalFiatBalance = totalFiatBalance, + sortedBy = sortType, + groups = nonEmptyStatuses.groupByNetworkAndSort(sortType), + ) + } + + private fun NonEmptyList.groupByNetworkAndSort(sortType: TokensSortType): List { + return this + .groupBy { it.currency.network } + .map { (network, currencies) -> + NetworkGroup( + network = network, + currencies = currencies.sortStatuses(sortType), + ) + } + .sortGroups(sortType) + } + + private fun List.sortGroups(type: TokensSortType): List { + return when (type) { + TokensSortType.NONE -> this + TokensSortType.BALANCE -> { + val hasLoading = this.asSequence() + .flatMap(NetworkGroup::currencies) + .any { it.value is CryptoCurrencyStatus.Loading } + + if (hasLoading) return this + + sortedByDescending { group -> + group.currencies.sumOf { it.calculateBalance() } + } + } + } + } + + private fun List.sortStatuses(type: TokensSortType): List { + return when (type) { + TokensSortType.NONE -> this + TokensSortType.BALANCE -> { + val hasLoading = any { it.value is CryptoCurrencyStatus.Loading } + + if (hasLoading) return this + + sortedByDescending { it.calculateBalance() } + } + } + } + + private fun CryptoCurrencyStatus.calculateBalance(): BigDecimal { + val yieldBalance = value.yieldBalance as? YieldBalance.Data + val totalYieldBalance = yieldBalance?.getTotalWithRewardsStakingBalance(currency.network.rawId).orZero() + val totalFiatYieldBalance = totalYieldBalance.multiply(value.fiatRate.orZero()) + + return value.fiatAmount?.plus(totalFiatYieldBalance).orZero() + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt deleted file mode 100644 index 198890fd63..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt +++ /dev/null @@ -1,115 +0,0 @@ -package com.tangem.domain.tokens.operations - -import arrow.core.NonEmptyList -import com.tangem.domain.models.StatusSource -import com.tangem.domain.models.TotalFiatBalance -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.getResultStatusSource -import com.tangem.domain.models.staking.YieldBalance -import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance -import com.tangem.lib.crypto.BlockchainUtils -import com.tangem.utils.extensions.orZero -import java.math.BigDecimal - -internal class TokenListFiatBalanceOperations( - private val currencies: NonEmptyList, - private val isAnyTokenLoading: Boolean, -) { - - @Suppress("LoopWithTooManyJumpStatements") - fun calculateFiatBalance(): TotalFiatBalance { - var fiatBalance: TotalFiatBalance = TotalFiatBalance.Loading - if (isAnyTokenLoading) return fiatBalance - - for (token in currencies) { - val blockchainId = token.currency.network.rawId - when (val status = token.value) { - is CryptoCurrencyStatus.Loading -> { - fiatBalance = TotalFiatBalance.Loading - break - } - is CryptoCurrencyStatus.NoQuote, - is CryptoCurrencyStatus.MissedDerivation, - -> { - fiatBalance = TotalFiatBalance.Failed - break - } - is CryptoCurrencyStatus.Unreachable, - is CryptoCurrencyStatus.NoAmount, - -> { - if (BlockchainUtils.isIncludeToBalanceOnError(blockchainId)) { - fiatBalance = recalculateNoAccountBalance(status, fiatBalance) - } else { - fiatBalance = TotalFiatBalance.Failed - break - } - } - is CryptoCurrencyStatus.NoAccount -> { - fiatBalance = recalculateNoAccountBalance(status, fiatBalance) - } - is CryptoCurrencyStatus.Loaded -> { - fiatBalance = recalculateBalance(status, fiatBalance, blockchainId) - } - is CryptoCurrencyStatus.Custom -> { - fiatBalance = recalculateBalance(status, fiatBalance, blockchainId) - } - } - } - - return (fiatBalance as? TotalFiatBalance.Loaded)?.copy( - source = currencies.map { it.value.sources.total }.getResultStatusSource(), - ) ?: fiatBalance - } - - private fun recalculateNoAccountBalance( - status: CryptoCurrencyStatus.Value, - currentBalance: TotalFiatBalance, - ): TotalFiatBalance { - return (currentBalance as? TotalFiatBalance.Loaded)?.copy(isAllAmountsSummarized = false) - ?: TotalFiatBalance.Loaded( - amount = BigDecimal.ZERO, - isAllAmountsSummarized = false, - source = (status as? CryptoCurrencyStatus.NoAccount)?.sources?.total ?: StatusSource.ACTUAL, - ) - } - - private fun recalculateBalance( - status: CryptoCurrencyStatus.Loaded, - currentBalance: TotalFiatBalance, - blockchainId: String, - ): TotalFiatBalance { - return with(currentBalance) { - val yieldBalance = status.yieldBalance as? YieldBalance.Data - val stakingBalance = yieldBalance?.getTotalWithRewardsStakingBalance(blockchainId).orZero() - val fiatStakingBalance = status.fiatRate.times(stakingBalance) - (this as? TotalFiatBalance.Loaded)?.copy( - amount = this.amount + status.fiatAmount + fiatStakingBalance, - ) ?: TotalFiatBalance.Loaded( - amount = status.fiatAmount + fiatStakingBalance, - isAllAmountsSummarized = true, - source = status.sources.total, - ) - } - } - - private fun recalculateBalance( - status: CryptoCurrencyStatus.Custom, - currentBalance: TotalFiatBalance, - blockchainId: String, - ): TotalFiatBalance { - return with(currentBalance) { - val isTokenAmountCanBeSummarized = status.fiatAmount != null - val yieldBalance = (status.yieldBalance as? YieldBalance.Data) - ?.getTotalWithRewardsStakingBalance(blockchainId).orZero() - val fiatYieldBalance = status.fiatRate?.times(yieldBalance).orZero() - (this as? TotalFiatBalance.Loaded)?.copy( - amount = this.amount + status.fiatAmount.orZero() + fiatYieldBalance, - isAllAmountsSummarized = isTokenAmountCanBeSummarized, - ) ?: TotalFiatBalance.Loaded( - amount = status.fiatAmount.orZero() + fiatYieldBalance, - isAllAmountsSummarized = isTokenAmountCanBeSummarized, - source = StatusSource.ACTUAL, - ) - } - } -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt index 01eac8c1bb..e42588514f 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt @@ -1,11 +1,12 @@ package com.tangem.domain.tokens.operations -import arrow.core.* -import arrow.core.raise.Raise +import arrow.core.Either +import arrow.core.left import arrow.core.raise.either -import arrow.core.raise.withError +import arrow.core.right +import arrow.core.toNonEmptyListOrNull +import com.tangem.domain.models.TokensGroupType import com.tangem.domain.models.TokensSortType -import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.models.wallet.UserWalletId @@ -30,85 +31,13 @@ internal class TokenListOperations( } } - private fun Raise.createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList { + private fun createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList { val nonEmptyCurrencies = tokens.toNonEmptyListOrNull() ?: return TokenList.Empty - val isAnyTokenLoading = nonEmptyCurrencies.any { it.value is CryptoCurrencyStatus.Loading } - val fiatBalanceOperations = TokenListFiatBalanceOperations(nonEmptyCurrencies, isAnyTokenLoading) - - return createTokenList( - currencies = nonEmptyCurrencies, - fiatBalance = fiatBalanceOperations.calculateFiatBalance(), - isAnyTokenLoading = isAnyTokenLoading, - isGrouped = isGrouped, - isSortedByBalance = isSortedByBalance, - ) - } - - private fun Raise.createTokenList( - currencies: NonEmptyList, - fiatBalance: TotalFiatBalance, - isAnyTokenLoading: Boolean, - isGrouped: Boolean, - isSortedByBalance: Boolean, - ): TokenList { - val sortingOperations = TokenListSortingOperations( - currencies = currencies, - isAnyTokenLoading = isAnyTokenLoading, - sortByBalance = isSortedByBalance, - ) - - return createTokenList(sortingOperations, fiatBalance, isGrouped) - } - - private fun Raise.createTokenList( - sortingOperations: TokenListSortingOperations, - fiatBalance: TotalFiatBalance, - isGrouped: Boolean, - ): TokenList { - return if (isGrouped) { - createGroupedTokenList(sortingOperations, fiatBalance) - } else { - createUngroupedTokenList(sortingOperations, fiatBalance) - } - } - - private fun Raise.createUngroupedTokenList( - sortingOperations: TokenListSortingOperations, - fiatBalance: TotalFiatBalance, - ): TokenList.Ungrouped = TokenList.Ungrouped( - sortedBy = sortingOperations.getSortType(), - totalFiatBalance = fiatBalance, - currencies = withError( - transform = { e -> - Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) } - }, - block = { sortingOperations.getTokens().bind() }, - ), - ) - - private fun Raise.createGroupedTokenList( - sortingOperations: TokenListSortingOperations, - fiatBalance: TotalFiatBalance, - ): TokenList.GroupedByNetwork = TokenList.GroupedByNetwork( - sortedBy = sortingOperations.getSortType(), - totalFiatBalance = fiatBalance, - groups = withError( - transform = { e -> - Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) } - }, - block = { sortingOperations.getGroupedTokens().bind() }, - ), - ) - - private fun createUnsortedUngroupedTokenList( - tokens: List, - fiatBalance: TotalFiatBalance, - ): TokenList.Ungrouped { - return TokenList.Ungrouped( - sortedBy = TokensSortType.NONE, - totalFiatBalance = fiatBalance, - currencies = tokens, + return TokenListFactory.create( + statuses = nonEmptyCurrencies, + groupType = if (isGrouped) TokensGroupType.NETWORK else TokensGroupType.NONE, + sortType = if (isSortedByBalance) TokensSortType.BALANCE else TokensSortType.NONE, ) } @@ -133,19 +62,5 @@ internal class TokenListOperations( data class UnableToSortTokenList(val unsortedTokenList: TokenList.Ungrouped) : Error() data class DataError(val cause: Throwable) : Error() - - internal companion object { - - fun fromTokenListOperations( - e: TokenListSortingOperations.Error, - createUnsortedUngroupedTokenList: () -> TokenList.Ungrouped, - ): Error = when (e) { - is TokenListSortingOperations.Error.EmptyTokens, - is TokenListSortingOperations.Error.NetworkNotFound, - -> UnableToSortTokenList( - unsortedTokenList = createUnsortedUngroupedTokenList(), - ) - } - } } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt deleted file mode 100644 index 7669211249..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.tangem.domain.tokens.operations - -import arrow.core.Either -import arrow.core.NonEmptyList -import arrow.core.raise.Raise -import arrow.core.raise.either -import arrow.core.raise.ensure -import arrow.core.raise.ensureNotNull -import arrow.core.toNonEmptyListOrNull -import com.tangem.domain.models.TokensSortType -import com.tangem.domain.models.TotalFiatBalance -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.network.Network -import com.tangem.domain.models.staking.YieldBalance -import com.tangem.domain.models.tokenlist.TokenList -import com.tangem.domain.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup -import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance -import com.tangem.utils.extensions.orZero -import java.math.BigDecimal - -internal class TokenListSortingOperations( - private val currencies: List, - private val isAnyTokenLoading: Boolean, - private val sortByBalance: Boolean, -) { - - constructor( - tokenList: TokenList, - sortByBalance: Boolean = tokenList.sortedBy == TokensSortType.BALANCE, - isAnyTokenLoading: Boolean = tokenList.totalFiatBalance is TotalFiatBalance.Loading, - ) : this( - currencies = tokenList.flattenCurrencies(), - isAnyTokenLoading = isAnyTokenLoading, - sortByBalance = sortByBalance, - ) - - fun getGroupedTokens(): Either> = either { - ensure(currencies.isNotEmpty()) { Error.EmptyTokens } - - if (sortByBalance) { - groupAndSortTokensByBalance() - } else { - groupTokens() - } - } - - fun getTokens(): Either> = either { - val nonEmptyCurrencies = ensureNotNull(currencies.toNonEmptyListOrNull()) { - Error.EmptyTokens - } - - if (sortByBalance) sortTokensByBalance(nonEmptyCurrencies) else nonEmptyCurrencies - } - - fun getSortType(): TokensSortType = if (sortByBalance) TokensSortType.BALANCE else TokensSortType.NONE - - private fun Raise.groupTokens(): NonEmptyList { - val groupedTokens = currencies - .groupBy { it.currency.network } - .map { (network, tokens) -> - NetworkGroup( - network = network, - currencies = ensureNotNull(tokens.toNonEmptyListOrNull()) { Error.EmptyTokens }, - ) - } - .toNonEmptyListOrNull() - - return ensureNotNull(groupedTokens) { Error.EmptyTokens } - } - - private fun Raise.groupAndSortTokensByBalance(): NonEmptyList