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/build.gradle.kts b/app/build.gradle.kts index a046ec5c66..2de49eab54 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -28,6 +28,7 @@ android { packaging { jniLibs { useLegacyPackaging = true + excludes.add("lib/x86_64/libargon2.so") } resources.excludes.add("META-INF/LICENSE.md") resources.excludes.add("META-INF/NOTICE.md") @@ -96,6 +97,9 @@ configurations.all { force( "org.bouncycastle:bcpkix-jdk15on:1.70", + deps.kotlin.coroutines, + deps.kotlin.coroutines.jvm, + deps.kotlin.coroutines.android, ) } } @@ -111,6 +115,7 @@ dependencies { implementation(projects.domain.account) implementation(projects.domain.models) implementation(projects.domain.core) + api(projects.domain.common) implementation(projects.domain.card) implementation(projects.domain.demo) implementation(projects.domain.demo.models) @@ -154,6 +159,7 @@ dependencies { implementation(projects.domain.walletManager) implementation(projects.domain.walletManager.models) implementation(projects.domain.yieldSupply) + implementation(projects.domain.blockaid) implementation(projects.common) implementation(projects.common.routing) diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt index c27c9c93db..27761a7ceb 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -100,6 +100,12 @@ abstract class BaseTestCase : TestCase( value = false ) } + appPreferencesStore.editData { mutablePreferences -> + mutablePreferences.set( + key = PreferencesKeys.getShouldShowNotificationKey("EnablePushesReminderNotification"), + value = false + ) + } promoRepository.setNeverToShowWalletPromo(PromoId.Sepa) } apiEnvironmentRule.setup(apiConfigsManager) 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/BuyTokenDetailsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenDetailsPageObject.kt index 2b4e330704..4f0037cbab 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenDetailsPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenDetailsPageObject.kt @@ -3,15 +3,13 @@ package com.tangem.screens import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import com.tangem.common.BaseTestCase import com.tangem.core.ui.R -import com.tangem.core.ui.test.BaseButtonTestTags -import com.tangem.core.ui.test.BuyTokenDetailsScreenTestTags -import com.tangem.core.ui.test.NotificationTestTags -import com.tangem.core.ui.test.TopAppBarTestTags +import com.tangem.core.ui.test.* 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 androidx.compose.ui.test.hasTestTag as withTestTag +import com.tangem.features.onramp.impl.R as OnrampImplR class BuyTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : ComposeScreen(semanticsProvider = semanticsProvider) { @@ -68,31 +66,65 @@ class BuyTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProv useUnmergedTree = true } - val providerLoadingTitle: KNode = child { - hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_LOADING_TITLE) + val recommendedTitle: KNode = child { + hasText(getResourceString(OnrampImplR.string.onramp_recommended_title)) } - val providerLoadingText: KNode = child { - hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_LOADING_TEXT) - } - - val providerTitle: KNode = child { - hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_TITLE) + val bestRateIcon: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_ICON) useUnmergedTree = true } - val providerText: KNode = child { - hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_TEXT) + val bestRateTitle: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_TITLE) + useUnmergedTree = true + } + + val offerTokenAmount: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.OFFER_TOKEN_AMOUNT) + useUnmergedTree = true + } + + val timingIcon: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.TIMING_ICON) + useUnmergedTree = true + } + + val payWith: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.PAY_WITH) + useUnmergedTree = true + } + + val paymentMethodIcon: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.PAYMENT_METHOD_ICON) + useUnmergedTree = true + } + + val instantProcessingSpeedText: KNode = child { + hasText(getResourceString(R.string.onramp_instant_status)) + useUnmergedTree = true + } + + val providerName: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.PROVIDER_NAME) useUnmergedTree = true } val buyButton: KNode = child { - hasTestTag(BaseButtonTestTags.BUTTON) + hasTestTag(BaseButtonTestTags.TEXT) hasText(getResourceString(R.string.common_buy)) + useUnmergedTree = true } - val toSBlock: KNode = child { - hasTestTag(BuyTokenDetailsScreenTestTags.TOS_BLOCK) + val continueButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(R.string.common_continue)) + useUnmergedTree = true + } + + val allOffersButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(R.string.onramp_all_offers_button_title)) useUnmergedTree = true } } 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/SelectPaymentMethodPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SelectPaymentMethodPageObject.kt index 0739883572..b629ddae76 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/SelectPaymentMethodPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/SelectPaymentMethodPageObject.kt @@ -1,17 +1,14 @@ package com.tangem.screens -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.BaseBottomSheetTestTags +import com.tangem.core.ui.test.OnrampOffersBlockTestTags import com.tangem.core.ui.test.SelectPaymentMethodBottomSheetTestTags -import com.tangem.core.ui.test.TopAppBarTestTags -import com.tangem.core.ui.utils.LazyListItemPositionSemantics import com.tangem.features.onramp.impl.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.compose.node.element.lazylist.KLazyListNode import io.github.kakaocup.kakao.common.utilities.getResourceString import androidx.compose.ui.test.hasTestTag as withTestTag import androidx.compose.ui.test.hasText as withText @@ -19,30 +16,31 @@ import androidx.compose.ui.test.hasText as withText class SelectPaymentMethodPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : ComposeScreen(semanticsProvider = semanticsProvider) { - - private val lazyList = KLazyListNode( - semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(SelectPaymentMethodBottomSheetTestTags.LAZY_LIST) }, - itemTypeBuilder = { itemType(::LazyListItemNode) }, - positionMatcher = { position -> - SemanticsMatcher.expectValue( - LazyListItemPositionSemantics, - position - ) - } - ) - val title: KNode = child { - hasTestTag(TopAppBarTestTags.TITLE) - hasText(getResourceString(R.string.onramp_pay_with)) + hasTestTag(BaseBottomSheetTestTags.TITLE) + hasText(getResourceString(R.string.onramp_all_offers_button_title)) } - fun paymentMethodWithNameAndIcon(name: String): KNode { - return lazyList.child { - hasAnyDescendant(withText(name)) - hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD_ICON)) - useUnmergedTree = true - } + val subtitle: KNode = child { + hasTestTag(BaseBottomSheetTestTags.SUBTITLE) + hasText(getResourceString(R.string.onramp_payment_method_subtitle)) + } + + val closeButton: KNode = child { + hasTestTag(BaseBottomSheetTestTags.CLOSE_BUTTON) + } + + fun paymentMethodWithName(name: String): KNode = child { + hasTestTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD) + hasAnyDescendant(withText(name)) + hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD_ICON)) + hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.TIMING_ICON)) + hasAnyDescendant(withTestTag(OnrampOffersBlockTestTags.TIMING_TEXT)) + hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.TOKEN_AMOUNT)) + hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.PROVIDER_COUNT_ICON)) + hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.PROVIDER_COUNT_TEXT)) + hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.UP_TO_TEXT)) + useUnmergedTree = true } } diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SelectProviderPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SelectProviderPageObject.kt index 2c8a050019..0be8e56d9d 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/SelectProviderPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/SelectProviderPageObject.kt @@ -2,87 +2,78 @@ package com.tangem.screens import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import com.tangem.common.BaseTestCase -import com.tangem.core.ui.test.SelectProviderBottomSheetTestTags +import com.tangem.core.ui.test.BaseBottomSheetTestTags +import com.tangem.core.ui.test.BaseButtonTestTags +import com.tangem.core.ui.test.OnrampOffersBlockTestTags import com.tangem.features.onramp.impl.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 androidx.compose.ui.test.hasText as withText +import com.tangem.core.ui.R as CoreUiR class SelectProviderPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : ComposeScreen(semanticsProvider = semanticsProvider) { val title: KNode = child { - hasText(getResourceString(R.string.onramp_choose_provider_title_hint)) + hasTestTag(BaseBottomSheetTestTags.TITLE) + hasText(getResourceString(R.string.onramp_all_offers_button_title)) + } + + val subtitle: KNode = child { + hasTestTag(BaseBottomSheetTestTags.SUBTITLE) + hasText(getResourceString(R.string.express_provider)) useUnmergedTree = true } + val bestRateIcon: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_ICON) + useUnmergedTree = true + } + + val bestRateTitle: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_TITLE) + useUnmergedTree = true + } + + val offerTokenAmount: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.OFFER_TOKEN_AMOUNT) + useUnmergedTree = true + } + + val timingIcon: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.TIMING_ICON) + useUnmergedTree = true + } + + val payWith: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.PAY_WITH) + useUnmergedTree = true + } + + val youGetText: KNode = child { + hasText(getResourceString(R.string.onramp_title_you_get)) + } + val paymentMethodIcon: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_ICON) + hasTestTag(OnrampOffersBlockTestTags.PAYMENT_METHOD_ICON) useUnmergedTree = true } - val paymentMethodTitle: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_NAME) + val providerName: KNode = child { + hasTestTag(OnrampOffersBlockTestTags.PROVIDER_NAME) useUnmergedTree = true } - val paymentMethodName: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_NAME) + + val instantProcessingSpeedText: KNode = child { + hasText(getResourceString(com.tangem.core.ui.R.string.onramp_instant_status)) useUnmergedTree = true } - val paymentMethodExpandButton: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_EXPAND_BUTTON) - useUnmergedTree = true - } - - val availableProviderItem: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_ITEM) - useUnmergedTree = true - } - - val availableProviderName: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_NAME) - useUnmergedTree = true - } - - val tokenAmount: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.TOKEN_AMOUNT) - useUnmergedTree = true - } - - val unavailableProviderItem: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.UNAVAILABLE_PROVIDER_ITEM) - useUnmergedTree = true - } - - val unavailableProviderName: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.UNAVAILABLE_PROVIDER_NAME) - useUnmergedTree = true - } - - val moreProvidersIcon: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.MORE_PROVIDERS_ICON) - useUnmergedTree = true - } - - val moreProvidersText: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.MORE_PROVIDERS_TEXT) - useUnmergedTree = true - } - - val bestRateLabel: KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.BEST_RATE_LABEL) - useUnmergedTree = true - } - - fun availableProviderWithName(name: String, tokenAmount: String, rate: String): KNode = child { - hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_ITEM) - hasAnyChild(withText(name)) - hasAnyChild(withText(tokenAmount)) - hasAnyChild(withText(rate)) + val buyButton: KNode = child { + hasTestTag(BaseButtonTestTags.TEXT) + hasText(getResourceString(CoreUiR.string.common_buy)) useUnmergedTree = true } } 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/screens/WalletConnectDetailsBottonSheetPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/WalletConnectDetailsBottonSheetPageObject.kt index e4eb48fde7..6ad955d669 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/WalletConnectDetailsBottonSheetPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/WalletConnectDetailsBottonSheetPageObject.kt @@ -2,6 +2,7 @@ package com.tangem.screens import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.BaseBottomSheetTestTags import com.tangem.core.ui.test.BaseButtonTestTags import com.tangem.core.ui.test.WalletConnectBottomSheetTestTags import com.tangem.core.ui.test.WalletConnectDetailsBottomSheetTestTags @@ -15,18 +16,18 @@ class WalletConnectDetailsBottomSheetPageObject(semanticsProvider: SemanticsNode ComposeScreen(semanticsProvider = semanticsProvider) { val title: KNode = child { - hasTestTag(WalletConnectDetailsBottomSheetTestTags.TITLE) + hasTestTag(BaseBottomSheetTestTags.TITLE) hasText(getResourceString(WalletConnectImplR.string.wc_connected_app_title)) useUnmergedTree = true } val date: KNode = child { - hasTestTag(WalletConnectDetailsBottomSheetTestTags.DATE) + hasTestTag(BaseBottomSheetTestTags.SUBTITLE) useUnmergedTree = true } val closeButton: KNode = child { - hasTestTag(WalletConnectDetailsBottomSheetTestTags.CLOSE_BUTTON) + hasTestTag(BaseBottomSheetTestTags.CLOSE_BUTTON) useUnmergedTree = true } 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/BuyTokenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/BuyTokenTest.kt index abea9913ef..1cdfa840e5 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/BuyTokenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/BuyTokenTest.kt @@ -2,13 +2,12 @@ package com.tangem.tests import com.tangem.common.BaseTestCase import com.tangem.common.constants.TestConstants.TOTAL_BALANCE -import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.utils.resetWireMockScenarioState import com.tangem.common.utils.setWireMockScenarioState -import com.tangem.screens.* import com.tangem.scenarios.openMainScreen import com.tangem.scenarios.synchronizeAddresses +import com.tangem.screens.* import dagger.hilt.android.testing.HiltAndroidTest import io.qameta.allure.kotlin.AllureId import io.qameta.allure.kotlin.junit4.DisplayName @@ -74,7 +73,7 @@ class BuyTokenTest : BaseTestCase() { val otherCurrenciesTitle = "Other currencies" val australianDollar = "AUD" val fiatAmount = "1" - val tokenAmount = "POL 488.24938338" + val tokenAmount = "~488.24938338 POL" val scenarioName = "payment_methods" step("Reset WireMock scenario '$scenarioName'") { @@ -102,18 +101,6 @@ class BuyTokenTest : BaseTestCase() { step("Write fiat amount = '$fiatAmount'") { onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) } } - step("Assert 'Provider loading block' is displayed") { - onBuyTokenDetailsScreen { - providerLoadingTitle.assertIsDisplayed() - providerLoadingText.assertIsDisplayed() - } - } - step("Assert 'Provider block' is displayed") { - onBuyTokenDetailsScreen { - providerTitle.assertIsDisplayed() - providerText.assertIsDisplayed() - } - } step("Assert token amount = '$tokenAmount'") { onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) @@ -162,7 +149,7 @@ class BuyTokenTest : BaseTestCase() { val balance = TOTAL_BALANCE val euro = "EUR" val fiatAmount = "1" - val tokenAmount = "POL 488.24938338" + val tokenAmount = "~488.24938338 POL" val scenarioName = "payment_methods" step("Reset WireMock scenario '$scenarioName'") { @@ -199,27 +186,45 @@ class BuyTokenTest : BaseTestCase() { step("Assert fiat amount = '$fiatAmount'") { onBuyTokenDetailsScreen { fiatAmountTextField.assertTextContains(euro + fiatAmount) } } - step("Assert 'Provider loading block' is displayed") { - onBuyTokenDetailsScreen { - providerLoadingTitle.assertIsDisplayed() - providerLoadingText.assertIsDisplayed() - } - } - step("Assert 'Provider block' is displayed") { - onBuyTokenDetailsScreen { - providerTitle.assertIsDisplayed() - providerText.assertIsDisplayed() - } - } step("Assert token amount = '$tokenAmount'") { onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) } } - step("Assert 'ToS' block is displayed") { - onBuyTokenDetailsScreen { toSBlock.assertIsDisplayed()} + step("Click on 'Continue' button") { + onBuyTokenDetailsScreen { continueButton.clickWithAssertion() } + } + step("Assert 'Recommended' title is displayed") { + onBuyTokenDetailsScreen { recommendedTitle.clickWithAssertion() } + } + step("Assert 'Best rate' icon is displayed") { + onBuyTokenDetailsScreen { bestRateIcon.assertIsDisplayed() } + } + step("Assert 'Best rate' title is displayed") { + onBuyTokenDetailsScreen { bestRateTitle.assertIsDisplayed() } + } + step("Assert offer amount is displayed") { + onBuyTokenDetailsScreen { offerTokenAmount.assertIsDisplayed() } } step("Assert 'Buy' button is displayed") { onBuyTokenDetailsScreen { buyButton.assertIsDisplayed()} } + step("Assert timing icon is displayed") { + onBuyTokenDetailsScreen { timingIcon.assertIsDisplayed() } + } + step("Assert 'Instant' processing time text is displayed") { + onBuyTokenDetailsScreen { instantProcessingSpeedText.assertIsDisplayed() } + } + step("Assert provider name is displayed") { + onBuyTokenDetailsScreen { providerName.assertIsDisplayed() } + } + step("Assert 'Pay with' is displayed") { + onBuyTokenDetailsScreen { payWith.assertIsDisplayed() } + } + step("Assert payment method icon is displayed") { + onBuyTokenDetailsScreen { paymentMethodIcon.assertIsDisplayed() } + } + step("Assert 'All offers' button is displayed") { + onBuyTokenDetailsScreen { allOffersButton.assertIsDisplayed() } + } step("Assert 'Close' button in top bar is displayed") { onBuyTokenDetailsScreen { topBarCloseButton.assertIsDisplayed() } } @@ -314,13 +319,10 @@ class BuyTokenTest : BaseTestCase() { setupHooks().run { val tokenTitle = "Polygon" val balance = TOTAL_BALANCE - val paymentMethod = "Card" + val paymentMethod = "Invoice Revolut Pay" val fiatAmount = "1" val providerNameMercuryo = "Mercuryo" - val providerNameSimplex = "Simplex" - val tokenAmount = "POL 488.24938338" - val bestRate = "Best rate" - val rate = "-0.00%" + val tokenAmount = "~488.24938338 POL" val scenarioName = "payment_methods" step("Reset WireMock scenario '$scenarioName'") { @@ -348,58 +350,53 @@ class BuyTokenTest : BaseTestCase() { step("Write fiat amount = '$fiatAmount'") { onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) } } + step("Assert token amount = '$tokenAmount'") { + onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) } + } + step("Click on 'Continue' button") { + onBuyTokenDetailsScreen { continueButton.clickWithAssertion() } + } step("Assert 'Provider block' is displayed") { - onBuyTokenDetailsScreen { - providerTitle.assertIsDisplayed() - providerText.assertIsDisplayed() - } + onBuyTokenDetailsScreen { providerName.assertIsDisplayed() } } - step("Open 'Select Provider' bottom sheet") { - onBuyTokenDetailsScreen { providerTitle.performClick() } + step("Click on 'All offers' button") { + onBuyTokenDetailsScreen { allOffersButton.clickWithAssertion() } } - step("Assert available provider name is displayed") { - onSelectProviderBottomSheet { - flakySafely(WAIT_UNTIL_TIMEOUT) { - availableProviderItem.assertIsDisplayed() - } - } + step("Click on '$paymentMethod' payment method") { + onSelectPaymentMethodBottomSheet { paymentMethodWithName(paymentMethod).clickWithAssertion() } } - step("Click on 'Expand payment methods' button") { - onSelectProviderBottomSheet { paymentMethodExpandButton.clickWithAssertion() } - } - step("Click on payment method: '$paymentMethod'") { - onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(paymentMethod).clickWithAssertion() } - } - step("Assert 'Select Provider' bottom sheet title is displayed") { + step("Assert 'Provider' bottom sheet title is displayed") { onSelectProviderBottomSheet { title.assertIsDisplayed() } } + step("Assert 'Provider' bottom sheet subtitle is displayed") { + onSelectProviderBottomSheet { subtitle.assertIsDisplayed() } + } + step("Assert 'Best rate' icon is displayed") { + onSelectProviderBottomSheet { bestRateIcon.assertIsDisplayed() } + } + step("Assert 'Best rate' title is displayed") { + onSelectProviderBottomSheet { bestRateTitle.assertIsDisplayed() } + } + step("Assert offer token amount is displayed") { + onSelectProviderBottomSheet { offerTokenAmount.assertIsDisplayed() } + } + step("Assert 'Buy' button is displayed") { + onSelectProviderBottomSheet { buyButton.assertIsDisplayed() } + } + step("Assert timing icon is displayed") { + onSelectProviderBottomSheet { timingIcon.assertIsDisplayed() } + } + step("Assert 'Instant' processing time text is displayed") { + onSelectProviderBottomSheet { instantProcessingSpeedText.assertIsDisplayed() } + } + step("Assert 'Pay with' text is displayed") { + onSelectProviderBottomSheet { payWith.assertIsDisplayed() } + } step("Assert payment method icon is displayed") { onSelectProviderBottomSheet { paymentMethodIcon.assertIsDisplayed() } } - step("Assert payment method title is displayed") { - onSelectProviderBottomSheet { paymentMethodTitle.assertIsDisplayed() } - } - step("Assert payment method name is displayed") { - onSelectProviderBottomSheet { paymentMethodName.assertIsDisplayed() } - } - step("Assert provider with name: '$providerNameMercuryo' and rate: '$bestRate' is displayed") { - onSelectProviderBottomSheet { - availableProviderWithName(providerNameMercuryo, tokenAmount, bestRate).assertIsDisplayed() - } - } - step("Assert provider with name: '$providerNameSimplex' and rate: '$rate' is displayed") { - onSelectProviderBottomSheet { - availableProviderWithName(providerNameSimplex, tokenAmount, rate).assertIsDisplayed() - } - } - step("Assert 'More providers' icon is displayed") { - onSelectProviderBottomSheet { moreProvidersIcon.assertIsDisplayed() } - } - step("Assert 'More providers' text is displayed") { - onSelectProviderBottomSheet { moreProvidersText.assertIsDisplayed() } - } - step("Assert 'Best rate' label is displayed") { - onSelectProviderBottomSheet { bestRateLabel.assertIsDisplayed() } + step("Assert provider name: '$providerNameMercuryo'") { + onSelectProviderBottomSheet { providerName.assertTextContains(providerNameMercuryo) } } } } @@ -416,6 +413,7 @@ class BuyTokenTest : BaseTestCase() { val invoiceRevolutPay = "Invoice Revolut Pay" val sepa = "Sepa" val fiatAmount = "1" + val tokenAmount = "~488.24938338 POL" val scenarioName = "payment_methods" step("Reset WireMock scenario '$scenarioName'") { @@ -443,40 +441,39 @@ class BuyTokenTest : BaseTestCase() { step("Write fiat amount = '$fiatAmount'") { onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) } } + step("Assert token amount = '$tokenAmount'") { + onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) } + } + step("Click on 'Continue' button") { + onBuyTokenDetailsScreen { continueButton.clickWithAssertion() } + } step("Assert 'Provider block' is displayed") { - onBuyTokenDetailsScreen { - providerTitle.assertIsDisplayed() - providerText.assertIsDisplayed() - } + onBuyTokenDetailsScreen { providerName.assertIsDisplayed() } } - step("Open 'Select Provider' bottom sheet") { - onBuyTokenDetailsScreen { providerTitle.performClick() } - } - step("Click on 'Expand payment methods' button") { - onSelectProviderBottomSheet { paymentMethodExpandButton.clickWithAssertion() } + step("Click on 'All offers' button") { + onBuyTokenDetailsScreen { allOffersButton.clickWithAssertion() } } step("Assert 'Select Payment Method' bottom sheet title is displayed") { onSelectPaymentMethodBottomSheet { title.assertIsDisplayed() } } + step("Assert 'Select Payment Method' bottom sheet subtitle is displayed") { + onSelectPaymentMethodBottomSheet { subtitle.assertIsDisplayed() } + } + step("Assert 'Select Payment Method' bottom sheet close button is displayed") { + onSelectPaymentMethodBottomSheet { closeButton.assertIsDisplayed() } + } step("Assert payment method: '$card' is displayed") { - onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(card).assertIsDisplayed() } + onSelectPaymentMethodBottomSheet { paymentMethodWithName(card).assertIsDisplayed() } } step("Assert payment method: '$googlePay' is displayed") { - onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(googlePay).assertIsDisplayed() } + onSelectPaymentMethodBottomSheet { paymentMethodWithName(googlePay).assertIsDisplayed() } } step("Assert payment method: '$invoiceRevolutPay' is displayed") { - onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(invoiceRevolutPay).assertIsDisplayed() } + onSelectPaymentMethodBottomSheet { paymentMethodWithName(invoiceRevolutPay).assertIsDisplayed() } } step("Assert payment method: '$sepa' is displayed") { - onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(sepa).assertIsDisplayed() } - } - step("Press 'Back' button") { - onSelectPaymentMethodBottomSheet { device.uiDevice.pressBack() } - } - step("Assert 'Select Provider' bottom sheet title is displayed") { - onSelectProviderBottomSheet { title.assertIsDisplayed() } + onSelectPaymentMethodBottomSheet { paymentMethodWithName(sepa).assertIsDisplayed() } } } } - } \ 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 @@ + Timber.i("foregroundActivity: ${entry.key} | ${entry.value.isDestroyed}") + entry.value.isDestroyed == false } ?.value @@ -28,9 +28,9 @@ object ForegroundActivityObserver { override fun onActivityResumed(activity: Activity) { Timber.i("onActivityResumed ${activity::class}") - (activity as? AppCompatActivity)?.let { + if (activity is AppCompatActivity) { Timber.i("onActivityResumed store activity") - activities[activity::class] = it + activities[activity::class] = activity } } diff --git a/app/src/main/java/com/tangem/tap/LockTimerWorker.kt b/app/src/main/java/com/tangem/tap/LockTimerWorker.kt index f6b3c93141..85497cc56b 100644 --- a/app/src/main/java/com/tangem/tap/LockTimerWorker.kt +++ b/app/src/main/java/com/tangem/tap/LockTimerWorker.kt @@ -4,10 +4,10 @@ import android.content.Context import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.WorkerParameters +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable -import com.tangem.domain.core.wallets.UserWalletsListRepository import com.tangem.features.hotwallet.HotWalletFeatureToggles import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 4a26816830..32ea789431 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -7,10 +7,10 @@ import androidx.lifecycle.LifecycleOwner import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import com.tangem.common.routing.AppRoute +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable -import com.tangem.domain.core.wallets.UserWalletsListRepository import com.tangem.domain.wallets.usecase.ClearAllHotWalletContextualUnlockUseCase import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.tap.LockTimerWorker.Companion.TAG @@ -22,6 +22,7 @@ import kotlinx.coroutines.launch import timber.log.Timber import java.util.concurrent.TimeUnit import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes @Suppress("LongParameterList") internal class LockUserWalletsTimer( diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 24653ac2e5..9d6af9296f 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -38,7 +38,7 @@ import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.core.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.settings.SetGooglePayAvailabilityUseCase import com.tangem.domain.settings.SetGoogleServicesAvailabilityUseCase import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase @@ -264,7 +264,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { tangemSdkManager = injectedTangemSdkManager backupServiceHolder.createAndSetService(cardSdkConfigRepository.sdk, this) - backupService = backupServiceHolder.backupService.get()!! // will be deleted eventually + backupService = requireNotNull(backupServiceHolder.backupService.get()) // will be deleted eventually lockUserWalletsTimer = LockUserWalletsTimer( context = this, @@ -371,17 +371,15 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - val fromPush = intent?.extras?.containsKey(OPENED_FROM_GCM_PUSH) ?: false - if (fromPush) { + val isFromPush = intent.extras?.containsKey(OPENED_FROM_GCM_PUSH) == true + if (isFromPush) { analyticsEventsHandler.send(Push.PushNotificationOpened) } - if (intent != null) { - handleDeepLink(intent = intent, isFromOnNewIntent = true) - } + handleDeepLink(intent = intent, isFromOnNewIntent = true) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -406,8 +404,8 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } override fun dispatchTouchEvent(event: MotionEvent): Boolean { - val result = WindowObscurationObserver.dispatchTouchEvent(event, analyticsEventsHandler) - return if (result) super.dispatchTouchEvent(event) else false + val isHandled = WindowObscurationObserver.dispatchTouchEvent(event, analyticsEventsHandler) + return if (isHandled) super.dispatchTouchEvent(event) else false } override fun dispatchKeyEvent(event: KeyEvent): Boolean { @@ -451,9 +449,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { getPolkadotCheckHasImmortalUseCase() .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) .distinctUntilChanged() - .collect { + .collect { (_, hasImmortalTransaction) -> 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 8adc257a55..ed454e9111 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 val 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..58e979c7dc 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,7 @@ 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" +private const val ORDER_ID_KEY = "tangem_pay_order_id_key" @Singleton internal class DefaultTangemPayStorage @Inject constructor( @@ -54,24 +54,25 @@ 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) = + override suspend fun storeOrderId(customerWalletAddress: String, orderId: String) { withContext(dispatcherProvider.io) { secureStorage.store( - customerWalletAddress.encodeToByteArray(throwOnInvalidSequence = true), - DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY, + orderId.encodeToByteArray(throwOnInvalidSequence = true), + createOrderIdKey(customerWalletAddress), ) } + } - override suspend fun getCustomerWalletAddress(): String? = withContext(dispatcherProvider.io) { - secureStorage.get(DEFAULT_CUSTOMER_WALLET_ADDRESS_KEY)?.decodeToString(throwOnInvalidSequence = true) + override suspend fun getOrderId(customerWalletAddress: String): String? = withContext(dispatcherProvider.io) { + secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true) } override suspend fun clear(customerWalletAddress: String) = withContext(dispatcherProvider.io) { secureStorage.delete(createKey(customerWalletAddress)) + secureStorage.delete(createOrderIdKey(customerWalletAddress)) } - private fun createKey(cardId: String): String = "${DEFAULT_KEY}_$cardId" + private fun createKey(address: String): String = "${DEFAULT_KEY}_$address" + + private fun createOrderIdKey(address: String): String = "${ORDER_ID_KEY}_$address" } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/data/UserWalletsStoreRepositoryProxy.kt b/app/src/main/java/com/tangem/tap/data/UserWalletsStoreRepositoryProxy.kt index d41ea29466..f167597c1a 100644 --- a/app/src/main/java/com/tangem/tap/data/UserWalletsStoreRepositoryProxy.kt +++ b/app/src/main/java/com/tangem/tap/data/UserWalletsStoreRepositoryProxy.kt @@ -3,7 +3,7 @@ package com.tangem.tap.data import com.tangem.common.CompletionResult import com.tangem.common.catching import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.core.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/com/tangem/tap/di/data/UserWalletsStoreModule.kt b/app/src/main/java/com/tangem/tap/di/data/UserWalletsStoreModule.kt index 2fe50e3705..f08b09fb11 100644 --- a/app/src/main/java/com/tangem/tap/di/data/UserWalletsStoreModule.kt +++ b/app/src/main/java/com/tangem/tap/di/data/UserWalletsStoreModule.kt @@ -1,8 +1,8 @@ package com.tangem.tap.di.data import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.core.wallets.UserWalletsListRepository import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.tap.data.RuntimeUserWalletsStore import com.tangem.tap.data.UserWalletsStoreRepositoryProxy diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index a674e4ac97..f814746b0a 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -1,18 +1,16 @@ package com.tangem.tap.di.domain -import com.tangem.domain.card.* +import com.tangem.domain.card.DeleteSavedAccessCodesUseCase +import com.tangem.domain.card.ResetCardUseCase +import com.tangem.domain.card.SetCardWasScannedUseCase import com.tangem.domain.card.repository.CardRepository -import com.tangem.domain.wallets.derivations.DerivationsRepository -import com.tangem.domain.demo.models.DemoConfig +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.core.wallets.UserWalletsListRepository -import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase -import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase -import com.tangem.domain.wallets.usecase.HasMissedDerivationsUseCase -import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase -import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase +import com.tangem.domain.wallets.usecase.* import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt index 5f8a03b7d2..b84ebc02b6 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardLegacyDomainModule.kt @@ -2,8 +2,8 @@ package com.tangem.tap.di.domain import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.domain.card.ScanCardProcessor +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.core.wallets.UserWalletsListRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt index 1989f4868e..4a0fe276d8 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -1,7 +1,7 @@ package com.tangem.tap.di.domain import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.domain.wallets.derivations.DerivationsRepository +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.markets.* import com.tangem.domain.markets.repositories.MarketsTokenRepository import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher @@ -12,8 +12,8 @@ import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.core.wallets.UserWalletsListRepository import com.tangem.features.hotwallet.HotWalletFeatureToggles import dagger.Module import dagger.Provides 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/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 6304510867..2df609823f 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -1,6 +1,6 @@ package com.tangem.tap.di.domain -import com.tangem.domain.core.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.transaction.WalletAddressServiceRepository 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..8bc8f71489 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 @@ -1,11 +1,11 @@ package com.tangem.tap.di.domain +import com.tangem.domain.blockaid.BlockAidGasEstimate import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.error.FeeErrorResolver +import com.tangem.domain.yield.supply.YieldSupplyErrorResolver import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository -import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase -import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase -import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase +import com.tangem.domain.yield.supply.usecase.* import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -20,9 +20,11 @@ internal object YieldSupplyDomainModule { @Singleton fun provideYieldSupplyStartEarningUseCase( yieldSupplyTransactionRepository: YieldSupplyTransactionRepository, + yieldSupplyErrorResolver: YieldSupplyErrorResolver, ): YieldSupplyStartEarningUseCase { return YieldSupplyStartEarningUseCase( yieldSupplyTransactionRepository = yieldSupplyTransactionRepository, + yieldSupplyErrorResolver = yieldSupplyErrorResolver, ) } @@ -30,9 +32,11 @@ internal object YieldSupplyDomainModule { @Singleton fun provideYieldSupplyStopEarningUseCase( yieldSupplyTransactionRepository: YieldSupplyTransactionRepository, + yieldSupplyErrorResolver: YieldSupplyErrorResolver, ): YieldSupplyStopEarningUseCase { return YieldSupplyStopEarningUseCase( yieldSupplyTransactionRepository = yieldSupplyTransactionRepository, + yieldSupplyErrorResolver = yieldSupplyErrorResolver, ) } @@ -41,10 +45,36 @@ internal object YieldSupplyDomainModule { fun provideYieldSupplyEstimateEnterFeeUseCase( feeRepository: FeeRepository, feeErrorResolver: FeeErrorResolver, + blockAidGasEstimate: BlockAidGasEstimate, ): YieldSupplyEstimateEnterFeeUseCase { return YieldSupplyEstimateEnterFeeUseCase( feeRepository = feeRepository, feeErrorResolver = feeErrorResolver, + blockAidGasEstimate = blockAidGasEstimate, + ) + } + + @Provides + @Singleton + fun provideYieldSupplyGetContractAddressUseCase( + yieldSupplyTransactionRepository: YieldSupplyTransactionRepository, + yieldSupplyErrorResolver: YieldSupplyErrorResolver, + ): YieldSupplyGetContractAddressUseCase { + return YieldSupplyGetContractAddressUseCase( + yieldSupplyTransactionRepository = yieldSupplyTransactionRepository, + yieldSupplyErrorResolver = yieldSupplyErrorResolver, + ) + } + + @Provides + @Singleton + fun provideYieldSupplyGetProtocolBalanceUseCase( + yieldSupplyTransactionRepository: YieldSupplyTransactionRepository, + yieldSupplyErrorResolver: YieldSupplyErrorResolver, + ): YieldSupplyGetProtocolBalanceUseCase { + return YieldSupplyGetProtocolBalanceUseCase( + yieldSupplyTransactionRepository = yieldSupplyTransactionRepository, + yieldSupplyErrorResolver = yieldSupplyErrorResolver, ) } } \ 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/di/UserWalletsListManagerModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt index c521709443..5ea3c3c88b 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt @@ -8,13 +8,13 @@ import com.tangem.common.json.TangemSdkAdapter import com.tangem.common.services.secure.SecureStorage import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.scan.serialization.* import com.tangem.domain.visa.model.VisaActivationRemoteState import com.tangem.domain.visa.model.VisaCardActivationStatus +import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository import com.tangem.domain.wallets.hot.HotWalletPasswordRequester import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.core.wallets.UserWalletsListRepository -import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository import com.tangem.sdk.storage.AndroidSecureStorage import com.tangem.sdk.storage.AndroidSecureStorageV2 import com.tangem.sdk.storage.createEncryptedSharedPreferences 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..36b368b111 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 @@ -4,17 +4,13 @@ import arrow.core.Either import arrow.core.left import arrow.core.raise.either import arrow.core.right -import com.tangem.common.CompletionResult -import com.tangem.common.doOnFailure -import com.tangem.common.doOnSuccess -import com.tangem.common.flatMap -import com.tangem.common.map +import com.tangem.common.* import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getSyncOrDefault -import com.tangem.domain.core.wallets.UserWalletsListRepository -import com.tangem.domain.core.wallets.UserWalletsListRepository.LockMethod -import com.tangem.domain.core.wallets.error.* +import com.tangem.domain.common.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod +import com.tangem.domain.common.wallets.error.* import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isLocked @@ -125,7 +121,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 +328,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 +391,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..25964c3070 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 @@ -6,7 +6,7 @@ import com.tangem.common.doOnSuccess import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.Analytics import com.tangem.domain.apptheme.model.AppThemeMode -import com.tangem.domain.core.wallets.UserWalletsListRepository.LockMethod +import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.wallet.UserWallet import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -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..06eda3e12c 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,24 +341,30 @@ private class AppCurrencySelectorStateProvider : CollectionPreviewParameterProvi .mapIndexed { index, s -> Currency(index.toString(), s) } .toPersistentList() - AppCurrencySelectorState.Loading(onBackClick = {}).let(::add) - AppCurrencySelectorState.Default( - selectedId = "0", - items = items, - scrollToSelected = consumedEvent(), - onCurrencyClick = {}, - onBackClick = {}, - onTopBarActionClick = {}, - ).let(::add) - AppCurrencySelectorState.Search( - selectedId = "0", - items = items, - scrollToSelected = consumedEvent(), - onCurrencyClick = {}, - onBackClick = {}, - onSearchInputChange = {}, - onTopBarActionClick = {}, - ).let(::add) + add(AppCurrencySelectorState.Loading(onBackClick = {})) + + add( + AppCurrencySelectorState.Default( + selectedId = "0", + items = items, + scrollToSelected = consumedEvent(), + onCurrencyClick = {}, + onBackClick = {}, + onTopBarActionClick = {}, + ), + ) + + add( + AppCurrencySelectorState.Search( + selectedId = "0", + items = items, + scrollToSelected = consumedEvent(), + onCurrencyClick = {}, + onBackClick = {}, + onSearchInputChange = {}, + onTopBarActionClick = {}, + ), + ) }, ) // 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..438819ed0a 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,12 @@ private class AppSettingsScreenStateProvider : CollectionPreviewParameterProvide itemsFactory.createSelectThemeModeButton(AppThemeMode.DEFAULT, {}), ) - AppSettingsScreenState.Content( - items = items, - dialog = null, - ).let(::add) + add( + AppSettingsScreenState.Content( + items = items, + dialog = null, + ), + ) }, ) // 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) + add(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..153b026e85 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,37 @@ private class SwitchItemProvider : CollectionPreviewParameterProvider { val items = buildList { - if (state.needEnrollBiometrics) { - itemsFactory.createEnrollBiometricsCard( - onClick = ::enrollBiometrics, - ).let(::add) - } + addIf( + condition = state.needEnrollBiometrics, + element = itemsFactory.createEnrollBiometricsCard(onClick = ::enrollBiometrics), + ) - itemsFactory.createSelectAppCurrencyButton( - currentAppCurrencyName = state.selectedAppCurrency.name, - onClick = ::showAppCurrencySelector, - ).let(::add) + add( + itemsFactory.createSelectAppCurrencyButton( + currentAppCurrencyName = state.selectedAppCurrency.name, + onClick = ::showAppCurrencySelector, + ), + ) if (hotWalletFeatureToggles.isHotWalletEnabled) { val canUseBiometrics = !state.needEnrollBiometrics && !state.isInProgress - itemsFactory.createUseBiometricsSwitch( - isChecked = state.useBiometricAuthentication, - isEnabled = canUseBiometrics, - onCheckedChange = ::onBiometricAuthenticationToggled, - ).let(::add) + add( + itemsFactory.createUseBiometricsSwitch( + isChecked = state.useBiometricAuthentication, + isEnabled = canUseBiometrics, + onCheckedChange = ::onBiometricAuthenticationToggled, + ), + ) - itemsFactory.createRequireAccessCodeSwitch( - isChecked = state.requireAccessCode, - isEnabled = canUseBiometrics && state.useBiometricAuthentication, - onCheckedChange = ::onRequireAccessCodeToggled, - ).let(::add) + add( + itemsFactory.createRequireAccessCodeSwitch( + isChecked = state.requireAccessCode, + isEnabled = canUseBiometrics && state.useBiometricAuthentication, + onCheckedChange = ::onRequireAccessCodeToggled, + ), + ) } else { if (state.isBiometricsAvailable) { val canUseBiometrics = !state.needEnrollBiometrics && !state.isInProgress - itemsFactory.createSaveWalletsSwitch( - isChecked = state.saveWallets, - isEnabled = canUseBiometrics, - onCheckedChange = ::onSaveWalletsToggled, - ).let(::add) + add( + itemsFactory.createSaveWalletsSwitch( + isChecked = state.saveWallets, + isEnabled = canUseBiometrics, + onCheckedChange = ::onSaveWalletsToggled, + ), + ) - itemsFactory.createSaveAccessCodeSwitch( - isChecked = state.saveAccessCodes, - isEnabled = canUseBiometrics, - onCheckedChange = ::onSaveAccessCodesToggled, - ).let(::add) + add( + itemsFactory.createSaveAccessCodeSwitch( + isChecked = state.saveAccessCodes, + isEnabled = canUseBiometrics, + onCheckedChange = ::onSaveAccessCodesToggled, + ), + ) } } - itemsFactory.createFlipToHideBalanceSwitch( - isChecked = state.isHidingEnabled, - isEnabled = true, - onCheckedChange = ::onFlipToHideBalanceToggled, - ).let(::add) + add( + itemsFactory.createFlipToHideBalanceSwitch( + isChecked = state.isHidingEnabled, + isEnabled = true, + onCheckedChange = ::onFlipToHideBalanceToggled, + ), + ) - itemsFactory.createSelectThemeModeButton( - currentThemeMode = state.selectedThemeMode, - onClick = { showThemeModeSelector(state.selectedThemeMode) }, - ).let(::add) + add( + itemsFactory.createSelectThemeModeButton( + currentThemeMode = state.selectedThemeMode, + onClick = { showThemeModeSelector(state.selectedThemeMode) }, + ), + ) } return items.toImmutableList() @@ -280,7 +294,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 +304,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..f1aa594af8 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 @@ -2,8 +2,8 @@ package com.tangem.tap.network.auth.di import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.core.wallets.UserWalletsListRepository import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.StakeKitAuthProvider @@ -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/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index ef68891955..0b32197bce 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -21,7 +21,7 @@ import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.core.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.feedback.GetWalletMetaInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase 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..7025664f0b 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 @@ -19,7 +19,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.card.repository.CardRepository -import com.tangem.domain.core.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.features.hotwallet.HotAccessCodeRequestComponent @@ -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..56a3421686 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, ) }, ), @@ -197,7 +200,7 @@ internal class ChildFactory @Inject constructor( createComponentChild( context = context, params = OnrampComponent.Params( - userWalletId = route.userWalletId, + userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param, cryptoCurrency = route.currency, source = route.source, launchSepa = route.launchSepa, @@ -271,7 +274,7 @@ internal class ChildFactory @Inject constructor( createComponentChild( context = context, params = TokenDetailsComponent.Params( - userWalletId = route.userWalletId, + userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param currency = route.currency, ), componentFactory = tokenDetailsComponentFactory, @@ -281,7 +284,7 @@ internal class ChildFactory @Inject constructor( createComponentChild( context = context, params = StakingComponent.Params( - userWalletId = route.userWalletId, + userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param, cryptoCurrencyId = route.cryptoCurrencyId, yieldId = route.yieldId, ), @@ -294,7 +297,7 @@ internal class ChildFactory @Inject constructor( params = SwapComponent.Params( currencyFrom = route.currencyFrom, currencyTo = route.currencyTo, - userWalletId = route.userWalletId, + userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param, isInitialReverseOrder = route.isInitialReverseOrder, screenSource = route.screenSource, ), @@ -305,7 +308,7 @@ internal class ChildFactory @Inject constructor( createComponentChild( context = context, params = SendComponent.Params( - userWalletId = route.userWalletId, + userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param, currency = route.currency, transactionId = route.transactionId, amount = route.amount, @@ -590,17 +593,27 @@ internal class ChildFactory @Inject constructor( is AppRoute.TangemPayDetails -> { createComponentChild( context = context, - params = TangemPayDetailsComponent.Params(userWalletId = route.userWalletId), + params = TangemPayDetailsComponent.Params(customerWalletAddress = route.customerWalletAddress), componentFactory = tangemPayDetailsComponentFactory, ) } 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..3893f51268 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 @@ -1,5 +1,6 @@ package com.tangem.common.routing +import android.annotation.SuppressLint import android.os.Bundle import com.tangem.common.routing.bundle.RouteBundleParams import com.tangem.common.routing.bundle.bundle @@ -8,6 +9,7 @@ import com.tangem.core.decompose.navigation.Route import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.feedback.models.WalletMetaInfo import com.tangem.domain.markets.TokenMarketParams +import com.tangem.domain.models.PortfolioId import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.scan.ScanResponse @@ -16,6 +18,7 @@ import com.tangem.domain.nft.models.NFTAsset import com.tangem.domain.onramp.model.OnrampSource import kotlinx.serialization.Serializable +@SuppressLint("UnsafeOptInUsageError") @Serializable sealed class AppRoute(val path: String) : Route { @@ -46,25 +49,50 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class CurrencyDetails( - val userWalletId: UserWalletId, + val portfolioId: PortfolioId, val currency: CryptoCurrency, - ) : AppRoute(path = "/currency_details/${userWalletId.stringValue}/${currency.id.value}") + ) : AppRoute(path = "/currency_details/${portfolioId.stringValue}/${currency.id.value}") { + companion object { + operator fun invoke(userWalletId: UserWalletId, currency: CryptoCurrency) = CurrencyDetails( + portfolioId = PortfolioId(userWalletId), + currency = currency, + ) + } + } @Serializable data class Send( - val userWalletId: UserWalletId, + val portfolioId: PortfolioId, val currency: CryptoCurrency, val transactionId: String? = null, val amount: String? = null, val tag: String? = null, val destinationAddress: String? = null, ) : AppRoute( - path = "/send/${userWalletId.stringValue}/${currency.id.value}?" + + path = "/send/${portfolioId.stringValue}/${currency.id.value}?" + "&$transactionId" + "&$amount" + "&$tag" + "&$destinationAddress", - ) + ) { + companion object { + operator fun invoke( + userWalletId: UserWalletId, + currency: CryptoCurrency, + transactionId: String? = null, + amount: String? = null, + tag: String? = null, + destinationAddress: String? = null, + ) = Send( + portfolioId = PortfolioId(userWalletId), + currency = currency, + transactionId = transactionId, + amount = amount, + tag = tag, + destinationAddress = destinationAddress, + ) + } + } @Serializable data class Details( @@ -169,26 +197,51 @@ sealed class AppRoute(val path: String) : Route { data class Swap( val currencyFrom: CryptoCurrency, val currencyTo: CryptoCurrency? = null, - val userWalletId: UserWalletId, + val portfolioId: PortfolioId, val isInitialReverseOrder: Boolean = false, val screenSource: String, ) : AppRoute( path = "/swap" + "/${currencyFrom.id.value}" + "/${currencyTo?.id?.value}" + - "/${userWalletId.stringValue}" + + "/${portfolioId.stringValue}" + "/$isInitialReverseOrder", - ) + ) { + companion object { + operator fun invoke( + userWalletId: UserWalletId, + currencyFrom: CryptoCurrency, + currencyTo: CryptoCurrency? = null, + isInitialReverseOrder: Boolean = false, + screenSource: String, + ) = Swap( + portfolioId = PortfolioId(userWalletId), + currencyFrom = currencyFrom, + currencyTo = currencyTo, + isInitialReverseOrder = isInitialReverseOrder, + screenSource = screenSource, + ) + } + } @Serializable data object AppCurrencySelector : AppRoute(path = "/app_currency_selector") @Serializable data class Staking( - val userWalletId: UserWalletId, + val portfolioId: PortfolioId, val cryptoCurrencyId: CryptoCurrency.ID, val yieldId: String, - ) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/$yieldId") + ) : AppRoute(path = "/staking/${portfolioId.stringValue}/${cryptoCurrencyId.value}/$yieldId") { + companion object { + operator fun invoke(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, yieldId: String) = + Staking( + portfolioId = PortfolioId(userWalletId), + cryptoCurrencyId = cryptoCurrencyId, + yieldId = yieldId, + ) + } + } @Serializable data class PushNotification( @@ -232,11 +285,25 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class Onramp( val source: OnrampSource, - val userWalletId: UserWalletId, + val portfolioId: PortfolioId, val currency: CryptoCurrency, val launchSepa: Boolean = false, - ) : AppRoute(path = "/onramp/${userWalletId.stringValue}/${currency.symbol}"), RouteBundleParams { + ) : AppRoute(path = "/onramp/${portfolioId.stringValue}/${currency.symbol}"), RouteBundleParams { override fun getBundle(): Bundle = bundle(serializer()) + + companion object { + operator fun invoke( + source: OnrampSource, + userWalletId: UserWalletId, + currency: CryptoCurrency, + launchSepa: Boolean = false, + ) = Onramp( + source = source, + portfolioId = PortfolioId(userWalletId), + currency = currency, + launchSepa = launchSepa, + ) + } } @Serializable @@ -372,12 +439,27 @@ sealed class AppRoute(val path: String) : Route { ) : AppRoute(path = "/archived_account/${userWalletId.stringValue}") @Serializable - data class TangemPayDetails(val userWalletId: UserWalletId) : AppRoute(path = "/tangem_pay_details") + data class TangemPayDetails(val customerWalletAddress: String) : AppRoute(path = "/tangem_pay_details") @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/test/src/main/java/com/tangem/common/test/utils/TruthExt.kt b/common/test/src/main/java/com/tangem/common/test/utils/TruthExt.kt index 67cd37711b..3349cef006 100644 --- a/common/test/src/main/java/com/tangem/common/test/utils/TruthExt.kt +++ b/common/test/src/main/java/com/tangem/common/test/utils/TruthExt.kt @@ -1,6 +1,8 @@ package com.tangem.common.test.utils import arrow.core.Either +import arrow.core.None +import arrow.core.Option import com.google.common.truth.Truth fun assertEither(actual: Either, expected: Either) { @@ -29,4 +31,16 @@ fun assertEitherLeft(actual: Either, expected: Throwable) { Truth.assertThat(it::class.java).isEqualTo(expected::class.java) Truth.assertThat(it).hasMessageThat().isEqualTo(expected.message) } +} + +fun assertNone(actual: Option) { + Truth.assertThat(actual).isEqualTo(None) +} + +fun assertSome(actual: Option, expected: B) { + actual + .onNone { error("Actual is None") } + .onSome { + Truth.assertThat(it).isEqualTo(expected) + } } \ No newline at end of file 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/account/AccountPortfolioItemUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt new file mode 100644 index 0000000000..276468c38c --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt @@ -0,0 +1,85 @@ +package com.tangem.common.ui.account + +import com.tangem.common.ui.R +import com.tangem.common.ui.userwallet.state.UserWalletItemUM +import com.tangem.core.ui.extensions.pluralReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.utils.converter.Converter + +class AccountPortfolioItemUMConverter( + private val onClick: (AccountId) -> Unit, + private val appCurrency: AppCurrency? = null, + private val accountBalance: TotalFiatBalance? = null, + private val isBalanceHidden: Boolean = false, + private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None, +) : Converter { + + override fun convert(value: Account): UserWalletItemUM { + return with(value) { + UserWalletItemUM( + id = UserWalletId(value.accountId.value), + name = value.accountName.toUM().value, + information = getInfo(value), + balance = getBalanceInfo(), + isEnabled = true, + endIcon = endIcon, + onClick = { onClick(value.accountId) }, + imageState = getImageState(value), + label = null, + ) + } + } + + private fun getInfo(account: Account): UserWalletItemUM.Information.Loaded = when (account) { + is Account.CryptoPortfolio -> { + val text = pluralReference( + R.plurals.common_tokens_count, + count = account.tokensCount, + formatArgs = wrappedList(account.tokensCount), + ) + UserWalletItemUM.Information.Loaded(text) + } + } + + private fun getImageState(account: Account) = when (account) { + is Account.CryptoPortfolio -> UserWalletItemUM.ImageState.Account( + name = account.accountName.toUM().value, + icon = account.icon.toUM(), + ) + } + + private fun getBalanceInfo(): UserWalletItemUM.Balance { + return when { + isBalanceHidden -> UserWalletItemUM.Balance.Hidden + accountBalance == null -> UserWalletItemUM.Balance.Loading + else -> { + when (accountBalance) { + is TotalFiatBalance.Loading -> UserWalletItemUM.Balance.Loading + is TotalFiatBalance.Failed -> UserWalletItemUM.Balance.Failed + is TotalFiatBalance.Loaded -> { + if (appCurrency != null) { + val formattedAmount = accountBalance.amount.format { + fiat(appCurrency.code, appCurrency.symbol) + } + + UserWalletItemUM.Balance.Loaded( + value = formattedAmount, + isFlickering = accountBalance.source == StatusSource.CACHE, + ) + } else { + UserWalletItemUM.Balance.Failed + } + } + } + } + } + } +} \ No newline at end of file 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..67b1a66b35 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 @@ -148,13 +148,17 @@ sealed class NotificationUM(val config: NotificationConfig) { }, ) - data class ExistentialDeposit(val deposit: String, val onConfirmClick: () -> Unit) : Error( + data class ExistentialDeposit( + val deposit: String, + val onConfirmClick: () -> Unit, + val isActionable: Boolean, + ) : Error( title = resourceReference(R.string.send_notification_existential_deposit_title), subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)), buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( text = resourceReference(R.string.send_notification_leave_button, wrappedList(deposit)), onClick = onConfirmClick, - ), + ).takeIf { isActionable }, ) data class ReserveAmount(val amount: String) : Error( @@ -265,6 +269,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/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index c06f560367..8c68959636 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -206,6 +206,7 @@ object NotificationsFactory { NotificationUM.Error.ExistentialDeposit::class.java, ) }, + isActionable = balance > existentialDeposit, ), ) } 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/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt index ad44dcf7dd..fedba7d453 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt @@ -28,9 +28,12 @@ import androidx.compose.ui.unit.dp import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.common.ui.R +import com.tangem.common.ui.account.AccountIcon +import com.tangem.common.ui.account.AccountIconPreviewData import com.tangem.common.ui.userwallet.state.UserWalletItemUM import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.TextShimmer +import com.tangem.core.ui.components.account.AccountIconSize import com.tangem.core.ui.components.block.BlockCard import com.tangem.core.ui.components.block.TangemBlockCardColors import com.tangem.core.ui.components.label.Label @@ -198,7 +201,7 @@ private fun BalanceContent(balance: UserWalletItemUM.Balance, modifier: Modifier } } -@Suppress("MagicNumber") +@Suppress("MagicNumber", "LongMethod") @Composable fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modifier) { val imageModifier = modifier @@ -264,6 +267,11 @@ fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modi ) } } + is UserWalletItemUM.ImageState.Account -> AccountIcon( + name = imageState.name, + icon = imageState.icon, + size = AccountIconSize.Default, + ) } } @@ -426,6 +434,18 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/BlockAidScanOptions.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/BlockAidScanOptions.kt new file mode 100644 index 0000000000..630910b832 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/BlockAidScanOptions.kt @@ -0,0 +1,7 @@ +package com.tangem.datasource.api.common.blockaid.models.request + +enum class BlockAidScanOptions(val value: String) { + Simulation("simulation"), + Validation("validation"), + GasEstimation("gas_estimation"), +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/EvmTransactionScanRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/EvmTransactionScanRequest.kt index f0d853d793..82b699a611 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/EvmTransactionScanRequest.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/request/EvmTransactionScanRequest.kt @@ -14,9 +14,25 @@ data class EvmTransactionScanRequest( @Json(name = "metadata") val metadata: TransactionMetadata, ) +@JsonClass(generateAdapter = true) +data class EvmTransactionBulkScanRequest( + @Json(name = "chain") val chain: String, + @Json(name = "options") val options: List, + @Json(name = "metadata") val metadata: TransactionMetadata, + @Json(name = "data") val data: List, + @Json(name = "aggregated") val aggregated: Boolean = false, +) + @JsonClass(generateAdapter = true) data class RpcData( @Json(name = "jsonrpc") val jsonrpc: String = "2.0", @Json(name = "method") val method: String, @Json(name = "params") val params: List>, +) + +@JsonClass(generateAdapter = true) +data class Data( + @Json(name = "from") val from: String, + @Json(name = "to") val to: String, + @Json(name = "data") val data: String, ) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/GasEstimationResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/GasEstimationResponse.kt new file mode 100644 index 0000000000..4f4e622045 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/GasEstimationResponse.kt @@ -0,0 +1,14 @@ +package com.tangem.datasource.api.common.blockaid.models.response + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GasEstimationResponse( + @Json(name = "gas_estimation") val gasEstimation: GasEstimationItem, +) + +@JsonClass(generateAdapter = true) +data class GasEstimationItem( + @Json(name = "estimate") val estimate: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt index 58df8bc70a..cac3e4c967 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt @@ -26,6 +26,7 @@ sealed class ApiConfig { StakeKit, TangemPay, BlockAid, + YieldSupply, } private fun initializeId(): ID { @@ -35,6 +36,7 @@ sealed class ApiConfig { is StakeKit -> ID.StakeKit is TangemPay -> ID.TangemPay is BlockAid -> ID.BlockAid + is YieldSupply -> ID.YieldSupply } } 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..4a8e1db349 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, + 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/YieldSupply.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/YieldSupply.kt new file mode 100644 index 0000000000..774d36cf67 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/YieldSupply.kt @@ -0,0 +1,81 @@ +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 + +/** YieldSupply [ApiConfig] */ +internal class YieldSupply( + private val environmentConfigStorage: EnvironmentConfigStorage, + private val appVersionProvider: AppVersionProvider, + private val authProvider: AuthProvider, + private val appInfoProvider: AppInfoProvider, +) : ApiConfig() { + + override val defaultEnvironment: ApiEnvironment = getInitialEnvironment() + + override val environmentConfigs = listOf( + createDevEnvironment(), + createStageEnvironment(), + createMockedEnvironment(), + createProdEnvironment(), + ) + + private fun getInitialEnvironment(): ApiEnvironment { + return when (BuildConfig.BUILD_TYPE) { + MOCKED_BUILD_TYPE, + -> ApiEnvironment.MOCK + DEBUG_BUILD_TYPE, + INTERNAL_BUILD_TYPE, + EXTERNAL_BUILD_TYPE, + RELEASE_BUILD_TYPE, + -> ApiEnvironment.PROD + else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]") + } + } + + private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( + environment = ApiEnvironment.DEV, + baseUrl = "[REDACTED_ENV_URL]", + headers = createHeaders(ApiEnvironment.DEV), + ) + + private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( + environment = ApiEnvironment.STAGE, + baseUrl = "[REDACTED_ENV_URL]", + headers = createHeaders(ApiEnvironment.STAGE), + ) + + private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( + environment = ApiEnvironment.MOCK, + baseUrl = "[REDACTED_ENV_URL]", + headers = createHeaders(ApiEnvironment.MOCK), + ) + + private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( + environment = ApiEnvironment.PROD, + baseUrl = "https://yield.tangem.org/", + headers = createHeaders(ApiEnvironment.PROD), + ) + + 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, + 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..9e3d771b52 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 @@ -7,8 +7,11 @@ import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header import retrofit2.http.POST +import retrofit2.http.Path import retrofit2.http.Query +private const val TX_HISTORY_PAGING_DEFAULT_LIMIT = 20 + @Suppress("TooManyFunctions") interface TangemPayApi { @@ -107,6 +110,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 @@ -115,4 +125,16 @@ interface TangemPayApi { @POST("v1/deeplink/validate") suspend fun validateDeeplink(@Body body: DeeplinkValidityRequest): ApiResponse + + @GET("v1/order/{order_id}") + suspend fun getOrder( + @Header("Authorization") authHeader: String, + @Path("order_id") orderId: String, + ): ApiResponse + + @POST("v1/order") + suspend fun createOrder( + @Header("Authorization") authHeader: String, + @Body body: OrderRequest, + ): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/OrderRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/OrderRequest.kt new file mode 100644 index 0000000000..73725d5782 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/OrderRequest.kt @@ -0,0 +1,9 @@ +package com.tangem.datasource.api.pay.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class OrderRequest( + @Json(name = "wallet_address") val walletAddress: String, +) \ No newline at end of file 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/OrderResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/OrderResponse.kt new file mode 100644 index 0000000000..e75134b059 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/OrderResponse.kt @@ -0,0 +1,33 @@ +package com.tangem.datasource.api.pay.models.response + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class OrderResponse( + @Json(name = "result") val result: Result?, + @Json(name = "error") val error: String?, +) { + @JsonClass(generateAdapter = true) + data class Result( + @Json(name = "id") val id: String, + @Json(name = "customer_id") val customerId: String?, + @Json(name = "type") val type: String?, + @Json(name = "status") val status: String, + @Json(name = "step") val step: String?, + @Json(name = "data") val data: Data, + @Json(name = "step_change_code") val stepChangeCode: Int?, + @Json(name = "created_at") val createdAt: String?, + @Json(name = "updated_at") val updatedAt: String?, + ) { + @JsonClass(generateAdapter = true) + data class Data( + @Json(name = "type") val type: String?, + @Json(name = "specification_name") val specificationName: String?, + @Json(name = "customer_wallet_address") val customerWalletAddress: String, + @Json(name = "emboss_name") val embossName: String?, + @Json(name = "product_instance_id") val productInstanceId: String?, + @Json(name = "payment_account_id") val paymentAccountId: String?, + ) + } +} \ 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/YieldSupplyApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/YieldSupplyApi.kt new file mode 100644 index 0000000000..cc41d91e89 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/YieldSupplyApi.kt @@ -0,0 +1,16 @@ +package com.tangem.datasource.api.tangemTech + +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse +import com.tangem.datasource.api.tangemTech.models.YieldTokenStatusResponse +import retrofit2.http.GET +import retrofit2.http.Path + +interface YieldSupplyApi { + + @GET("v1/yield/markets") + suspend fun getYieldMarkets(): ApiResponse + + @GET("v1/yield/token/{tokenAddress}") + suspend fun getYieldTokenStatus(@Path("tokenAddress") tokenAddress: String): ApiResponse +} \ No newline at end of file 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/api/tangemTech/models/YieldMarketsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/YieldMarketsResponse.kt new file mode 100644 index 0000000000..7f816f3161 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/YieldMarketsResponse.kt @@ -0,0 +1,29 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.math.BigDecimal + +@JsonClass(generateAdapter = true) +data class YieldMarketsResponse( + @Json(name = "tokens") val marketDtos: List, + @Json(name = "lastUpdatedAt") val lastUpdated: String, +) { + + @JsonClass(generateAdapter = true) + data class MarketDto( + @Json(name = "tokenAddress") val tokenAddress: String, + @Json(name = "tokenSymbol") val tokenSymbol: String, + @Json(name = "tokenName") val tokenName: String, + @Json(name = "apy") val apy: BigDecimal, + @Json(name = "totalSupplied") val totalSupplied: String, + @Json(name = "totalBorrowed") val totalBorrowed: String, + @Json(name = "liquidityRate") val liquidityRate: String, + @Json(name = "borrowRate") val borrowRate: String, + @Json(name = "utilizationRate") val utilizationRate: BigDecimal, + @Json(name = "isActive") val isActive: Boolean, + @Json(name = "ltv") val ltv: BigDecimal, + @Json(name = "liquidationThreshold") val liquidationThreshold: BigDecimal, + @Json(name = "decimals") val decimals: Int, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/YieldTokenStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/YieldTokenStatusResponse.kt new file mode 100644 index 0000000000..8303a4a388 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/YieldTokenStatusResponse.kt @@ -0,0 +1,18 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.math.BigDecimal + +@JsonClass(generateAdapter = true) +data class YieldTokenStatusResponse( + @Json(name = "tokenAddress") val tokenAddress: String, + @Json(name = "tokenSymbol") val tokenSymbol: String, + @Json(name = "userBalance") val userBalance: String, + @Json(name = "earnedYield") val earnedYield: String, + @Json(name = "currentApy") val currentApy: BigDecimal, + @Json(name = "totalDeposited") val totalDeposited: String, + @Json(name = "moduleAddress") val moduleAddress: String, + @Json(name = "status") val status: String, + @Json(name = "lastUpdateAt") val lastUpdateAt: String, +) \ 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/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 6c8dc4b626..9a0bed416c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -15,6 +15,7 @@ import com.tangem.datasource.api.onramp.OnrampApi import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.stakekit.StakeKitApi import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.YieldSupplyApi import com.tangem.datasource.di.utils.RetrofitApiBuilder import com.tangem.datasource.di.utils.RetrofitApiBuilder.Timeouts import com.tangem.datasource.local.preferences.AppPreferencesStore @@ -88,6 +89,15 @@ internal object NetworkModule { ) } + @Provides + @Singleton + fun provideYieldSupplyApi(retrofitApiBuilder: RetrofitApiBuilder): YieldSupplyApi { + return retrofitApiBuilder.build( + apiConfigId = ApiConfig.ID.YieldSupply, + applyTimeoutAnnotations = true, + ) + } + @Provides @Singleton fun provideTangemTechMarketsApi(retrofitApiBuilder: RetrofitApiBuilder): TangemTechMarketsApi { 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/di/YieldSupplyModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/YieldSupplyModule.kt new file mode 100644 index 0000000000..8b174ebbc1 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/YieldSupplyModule.kt @@ -0,0 +1,45 @@ +package com.tangem.datasource.di + +import android.content.Context +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile +import com.squareup.moshi.Moshi +import com.tangem.datasource.local.yieldsupply.DefaultYieldMarketsStore +import com.tangem.datasource.local.yieldsupply.YieldMarketsStore +import com.tangem.datasource.utils.MoshiDataStoreSerializer +import com.tangem.datasource.utils.listTypes +import com.tangem.domain.yield.supply.models.YieldMarketToken +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object YieldSupplyModule { + + @Provides + @Singleton + fun provideYieldMarketsStore( + @NetworkMoshi moshi: Moshi, + @ApplicationContext context: Context, + dispatchers: CoroutineDispatcherProvider, + ): YieldMarketsStore { + return DefaultYieldMarketsStore( + persistenceStore = DataStoreFactory.create( + serializer = MoshiDataStoreSerializer( + moshi = moshi, + types = listTypes(), + defaultValue = emptyList(), + ), + produceFile = { context.dataStoreFile(fileName = "yield_markets_cache") }, + scope = CoroutineScope(context = dispatchers.io + SupervisorJob()), + ), + ) + } +} \ 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: String): String = key + + override suspend fun getSyncOrNull(key: String, cursor: String): List? { + val storedValue = getSyncOrNull(key) + return storedValue?.get(cursor) + } + + override suspend fun store(key: String, 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..1ed58f2220 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,9 @@ interface TangemPayStorage { suspend fun getAuthTokens(customerWalletAddress: String): VisaAuthTokens? - suspend fun storeCustomerWalletAddress(customerWalletAddress: String) + suspend fun storeOrderId(customerWalletAddress: String, orderId: String) - suspend fun getCustomerWalletAddress(): String? + suspend fun getOrderId(customerWalletAddress: String): 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..e18be41c5a --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayTxHistoryItemsStore.kt @@ -0,0 +1,12 @@ +package com.tangem.datasource.local.visa + +import com.tangem.domain.visa.model.TangemPayTxHistoryItem + +interface TangemPayTxHistoryItemsStore { + + suspend fun getSyncOrNull(key: String, cursor: String): List? + + suspend fun remove(key: String) + + suspend fun store(key: String, cursor: String, value: List) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/yieldsupply/DefaultYieldMarketsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/yieldsupply/DefaultYieldMarketsStore.kt new file mode 100644 index 0000000000..b282890523 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/yieldsupply/DefaultYieldMarketsStore.kt @@ -0,0 +1,21 @@ +package com.tangem.datasource.local.yieldsupply + +import androidx.datastore.core.DataStore +import com.tangem.domain.yield.supply.models.YieldMarketToken +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull + +internal class DefaultYieldMarketsStore( + private val persistenceStore: DataStore>, +) : YieldMarketsStore { + + override fun get(): Flow> = persistenceStore.data + + override suspend fun getSyncOrNull(): List? { + return persistenceStore.data.firstOrNull() + } + + override suspend fun store(items: List) { + persistenceStore.updateData { _ -> items } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/yieldsupply/YieldMarketsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/yieldsupply/YieldMarketsStore.kt new file mode 100644 index 0000000000..c78c131d7a --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/yieldsupply/YieldMarketsStore.kt @@ -0,0 +1,13 @@ +package com.tangem.datasource.local.yieldsupply + +import com.tangem.domain.yield.supply.models.YieldMarketToken +import kotlinx.coroutines.flow.Flow + +interface YieldMarketsStore { + + fun get(): Flow> + + suspend fun getSyncOrNull(): List? + + suspend fun store(items: 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 1459ffedd9..5727c04eb0 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1740,7 +1740,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/TangemModalBottomSheetTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetTitle.kt index c4f1b6ed48..972fe1ae4b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetTitle.kt @@ -21,7 +21,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.test.WalletConnectDetailsBottomSheetTestTags +import com.tangem.core.ui.test.BaseBottomSheetTestTags /** * Title component for [TangemModalBottomSheet] with [TangemIconButton] for buttons. @@ -58,7 +58,7 @@ fun TangemModalBottomSheetTitle( color = TangemTheme.colors.text.primary1, modifier = Modifier .align(Alignment.CenterHorizontally) - .testTag(WalletConnectDetailsBottomSheetTestTags.TITLE), + .testTag(BaseBottomSheetTestTags.TITLE), ) } if (subtitle != null) { @@ -68,7 +68,7 @@ fun TangemModalBottomSheetTitle( color = TangemTheme.colors.text.tertiary, modifier = Modifier .align(Alignment.CenterHorizontally) - .testTag(WalletConnectDetailsBottomSheetTestTags.DATE), + .testTag(BaseBottomSheetTestTags.SUBTITLE), ) } } @@ -79,7 +79,7 @@ fun TangemModalBottomSheetTitle( modifier = Modifier .padding(16.dp) .align(Alignment.CenterEnd) - .testTag(WalletConnectDetailsBottomSheetTestTags.CLOSE_BUTTON), + .testTag(BaseBottomSheetTestTags.CLOSE_BUTTON), ) } } 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/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index 50f8841ef5..94026d5ae3 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -256,6 +256,7 @@ private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButt modifier = Modifier.fillMaxWidth(), iconResId = config.iconResId, enabled = isEnabled, + showProgress = config.showProgress, ) } else { SecondaryButton( @@ -264,6 +265,7 @@ private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButt modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, enabled = isEnabled, + showProgress = config.showProgress, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt index bd4d9dacef..fab4badd0c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt @@ -44,6 +44,7 @@ data class NotificationConfig( val text: TextReference, @DrawableRes val iconResId: Int? = null, val onClick: () -> Unit, + val showProgress: Boolean = false, ) : ButtonsState() data class PairButtonsConfig( 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/BaseBottomSheetTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/BaseBottomSheetTestTags.kt index 2604a7ad0e..d2d4a850de 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/BaseBottomSheetTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/BaseBottomSheetTestTags.kt @@ -2,4 +2,7 @@ package com.tangem.core.ui.test object BaseBottomSheetTestTags { const val ACTION_TITLE = "BASE_BOTTOM_SHEET_ACTION_TITLE" + const val TITLE = "BASE_BOTTOM_SHEET_TITLE" + const val SUBTITLE = "BASE_BOTTOM_SHEET_SUBTITLE" + const val CLOSE_BUTTON = "BASE_BOTTOM_SHEET_CLOSE_BUTTON" } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/BuyTokenDetailsScreenTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/BuyTokenDetailsScreenTestTags.kt index 0f045c5da5..0a77a52a74 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/BuyTokenDetailsScreenTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/BuyTokenDetailsScreenTestTags.kt @@ -5,12 +5,4 @@ object BuyTokenDetailsScreenTestTags { const val FIAT_CURRENCY_ICON = "BUY_TOKEN_DETAILS_SCREEN_FIAT_CURRENCY_ICON" const val FIAT_AMOUNT_TEXT_FIELD = "BUY_TOKEN_DETAILS_SCREEN_FIAT_AMOUNT_TEXT_FIELD" const val TOKEN_AMOUNT = "BUY_TOKEN_DETAILS_SCREEN_TOKEN_AMOUNT" - - const val PROVIDER_LOADING_TITLE = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_LOADING_TITLE" - const val PROVIDER_LOADING_TEXT = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_LOADING_TITLE" - - const val PROVIDER_TITLE = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_TITLE" - const val PROVIDER_TEXT = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_TEXT" - - const val TOS_BLOCK = "BUY_TOKEN_DETAILS_SCREEN_TOS_BLOCK" } \ 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/OnrampOffersBlockTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/OnrampOffersBlockTestTags.kt new file mode 100644 index 0000000000..8a7390b1ec --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/OnrampOffersBlockTestTags.kt @@ -0,0 +1,12 @@ +package com.tangem.core.ui.test + +object OnrampOffersBlockTestTags { + const val PROVIDER_NAME = "ONRAMP_OFFERS_BLOCK_PROVIDER_TITLE" + const val BEST_RATE_ICON = "ONRAMP_OFFERS_BLOCK_BEST_RATE_ICON" + const val BEST_RATE_TITLE = "ONRAMP_OFFERS_BLOCK_BEST_RATE_TITLE" + const val OFFER_TOKEN_AMOUNT = "ONRAMP_OFFERS_BLOCK_OFFER_TOKEN_AMOUNT" + const val TIMING_ICON = "ONRAMP_OFFERS_BLOCK_TIMING_ICON" + const val PAY_WITH = "ONRAMP_OFFERS_BLOCK_PAY_WITH" + const val PAYMENT_METHOD_ICON = "ONRAMP_OFFERS_BLOCK_PAYMENT_METHOD_ICON" + const val TIMING_TEXT = "ONRAMP_OFFERS_BLOCK_TIMING_TEXT" +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/SelectPaymentMethodBottomSheetTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/SelectPaymentMethodBottomSheetTestTags.kt index ebb601e9a5..037713590b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/SelectPaymentMethodBottomSheetTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/SelectPaymentMethodBottomSheetTestTags.kt @@ -1,7 +1,13 @@ package com.tangem.core.ui.test object SelectPaymentMethodBottomSheetTestTags { - - const val LAZY_LIST = "SELECT_PAYMENT_METHOD_LAZY_LIST" - const val PAYMENT_METHOD_ICON = "PAYMENT_METHOD_NAME" + const val PAYMENT_METHOD = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_PAYMENT_METHOD" + const val PAYMENT_METHOD_ICON = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_PAYMENT_METHOD_ICON" + const val PAYMENT_METHOD_NAME = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_PAYMENT_METHOD_NAME" + const val UP_TO_TEXT = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_UP_TO_TEXT" + const val TOKEN_AMOUNT = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_TOKEN_AMOUNT" + const val BEST_RATE_ICON = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_BEST_RATE_ICON" + const val PROVIDER_COUNT_ICON = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_PROVIDER_COUNT_ICON" + const val PROVIDER_COUNT_TEXT = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_PROVIDER_COUNT_TEXT" + const val TIMING_ICON = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_TIMING_ICON" } \ No newline at end of file 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/test/WalletConnectDetailsBottomSheetTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/WalletConnectDetailsBottomSheetTestTags.kt index f5f7ad7776..b269554f43 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/WalletConnectDetailsBottomSheetTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/WalletConnectDetailsBottomSheetTestTags.kt @@ -1,9 +1,6 @@ package com.tangem.core.ui.test object WalletConnectDetailsBottomSheetTestTags { - const val TITLE = "WALLET_CONNECT_DETAILS_BOTTOM_SHEET_TITLE" - const val DATE = "WALLET_CONNECT_DETAILS_BOTTOM_SHEET_DATE" - const val CLOSE_BUTTON = "WALLET_CONNECT_DETAILS_BOTTOM_CLOSE_BUTTON" const val DISCONNECT_BUTTON = "WALLET_CONNECT_DETAILS_BOTTOM_SHEET_DISCONNECT_BUTTON" const val WALLET_ICON = "WALLET_CONNECT_DETAILS_BOTTOM_WALLET_ICON" const val WALLET_TITLE = "WALLET_CONNECT_DETAILS_BOTTOM_WALLET" 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/TangemLinks.kt b/core/utils/src/main/java/com/tangem/utils/TangemLinks.kt new file mode 100644 index 0000000000..3a1c60869e --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/TangemLinks.kt @@ -0,0 +1,21 @@ +package com.tangem.utils + +import java.util.Locale + +object TangemLinks { + + private const val RU_LOCALE = "ru" + private const val EN_LOCALE = "en" + + private const val TANGEM_MAIN = "https://tangem.com/" + + val FEE_BLOG_LINK: String + get(): String { + val locale = if (Locale.getDefault().language == RU_LOCALE) RU_LOCALE else EN_LOCALE + return buildString { + append(TANGEM_MAIN) + append(locale) + append("/blog/post/what-is-a-transaction-fee-and-why-do-we-need-it/") + } + } +} \ No newline at end of file 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/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt b/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt new file mode 100644 index 0000000000..acdc6e4578 --- /dev/null +++ b/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt @@ -0,0 +1,152 @@ +package com.tangem.data.account.tokens + +import arrow.core.Either +import arrow.core.raise.Raise +import arrow.core.raise.either +import arrow.core.raise.ensureNotNull +import arrow.core.toNonEmptyListOrNull +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.data.account.store.AccountsResponseStoreFactory +import com.tangem.data.account.utils.assignTokens +import com.tangem.data.account.utils.toUserTokensResponse +import com.tangem.data.common.account.WalletAccountsSaver +import com.tangem.data.common.currency.UserTokensSaver +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse +import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO +import com.tangem.datasource.utils.getSyncOrNull +import com.tangem.domain.account.tokens.MainAccountTokensMigration +import com.tangem.domain.models.account.DerivationIndex +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.lib.crypto.derivation.AccountNodeRecognizer +import timber.log.Timber + +/** + * Implementation of [MainAccountTokensMigration] for migrating tokens associated with a main account. + * The migration process involves transferring unassigned tokens from the main account to a selected account. + * + * @property accountsResponseStoreFactory Factory for creating stores to access cached account responses. + * @property userTokensSaver Saver for updating user tokens in persistent storage. + * @property walletAccountsSaver Saver for updating wallet accounts in persistent storage. + * +[REDACTED_AUTHOR] + */ +internal class DefaultMainAccountTokensMigration( + private val accountsResponseStoreFactory: AccountsResponseStoreFactory, + private val userTokensSaver: UserTokensSaver, + private val walletAccountsSaver: WalletAccountsSaver, +) : MainAccountTokensMigration { + + override suspend fun migrate( + userWalletId: UserWalletId, + derivationIndex: DerivationIndex, + ): Either = either { + if (derivationIndex == DerivationIndex.Main) { + Timber.i("Migration skipped: derivation index is Main") + return@either + } + + val store = accountsResponseStoreFactory.create(userWalletId) + + val response = store.getSyncOrNull() + + ensureNotNull(response) { + val exception = IllegalStateException("No cached accounts response found") + Timber.e(exception) + exception + } + + val mainAccount = findAccount(response = response, derivationIndex = DerivationIndex.Main) + val selectedAccount = findAccount(response = response, derivationIndex = derivationIndex) + + val unassignedTokens = mainAccount.findUnassignedTokens(derivationIndex) + + if (unassignedTokens == null) { + Timber.i("No unassigned tokens found for migration") + return@either + } + + val updatedResponse = response.copy( + accounts = response.accounts.map { account -> + when (account.id) { + mainAccount.id -> { + account.copy(tokens = account.tokens.orEmpty() - unassignedTokens) + } + selectedAccount.id -> { + selectedAccount.assignTokens(userWalletId, unassignedTokens) + } + else -> account + } + }, + ) + + walletAccountsSaver.store(userWalletId = userWalletId, response = updatedResponse) + + userTokensSaver.push( + userWalletId = userWalletId, + response = updatedResponse.toUserTokensResponse(), + onFailSend = { + // TODO: save failed state to retry later + // [REDACTED_JIRA] + val exception = IllegalStateException("Failed to push updated tokens after migration") + Timber.e(exception) + raise(exception) + }, + ) + } + + private fun Raise.findAccount( + response: GetWalletAccountsResponse, + derivationIndex: DerivationIndex, + ): WalletAccountDTO { + val account = response.accounts.firstOrNull { it.derivationIndex == derivationIndex.value } + + return ensureNotNull(account) { + val exception = IllegalStateException("No account found with derivation index: $derivationIndex") + Timber.e(exception) + exception + } + } + + private fun WalletAccountDTO.findUnassignedTokens( + derivationIndex: DerivationIndex, + ): List? { + val tokens = this.tokens + + if (tokens.isNullOrEmpty()) return tokens + + return tokens + .filterByDerivationIndex(derivationIndex) + .map { it.copy(accountId = null) } + .toNonEmptyListOrNull() + } + + private fun List.filterByDerivationIndex( + derivationIndex: DerivationIndex, + ): List { + return filter { savedToken -> + val blockchain = Blockchain.fromNetworkId(networkId = savedToken.networkId) + if (blockchain == null) { + Timber.e("Token has unknown networkId: $savedToken") + return@filter false + } + + val derivationPathValue = savedToken.derivationPath + if (derivationPathValue == null) { + Timber.e("Token has no derivation path: $savedToken") + return@filter false + } + + val accountNodeRecognizer = AccountNodeRecognizer(blockchain) + val accountNodeValue = accountNodeRecognizer.recognize(derivationPathValue) + + if (accountNodeValue == null) { + Timber.e("Token has unrecognized derivation path: $savedToken") + return@filter false + } + + accountNodeValue == derivationIndex.value.toLong() + } + } +} \ No newline at end of file diff --git a/data/account/src/main/kotlin/com/tangem/data/account/utils/GetWalletAccountsResponseExt.kt b/data/account/src/main/kotlin/com/tangem/data/account/utils/GetWalletAccountsResponseExt.kt index caace442d6..ee6c308a81 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/utils/GetWalletAccountsResponseExt.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/utils/GetWalletAccountsResponseExt.kt @@ -54,4 +54,14 @@ internal fun List.assignTokens( tokens = enrichedTokens[accountDTO.id].orEmpty(), ) } +} + +internal fun WalletAccountDTO.assignTokens( + userWalletId: UserWalletId, + tokens: List, +): WalletAccountDTO { + val enrichedTokens = UserTokensResponseAccountIdEnricher(userWalletId, tokens) + .filter { it.accountId == this.id } + + return copy(tokens = enrichedTokens) } \ No newline at end of file diff --git a/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt b/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt new file mode 100644 index 0000000000..685272f7ac --- /dev/null +++ b/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt @@ -0,0 +1,234 @@ +package com.tangem.data.account.token + +import com.tangem.common.test.utils.assertEitherLeft +import com.tangem.common.test.utils.assertEitherRight +import com.tangem.data.account.converter.createGetWalletAccountsResponse +import com.tangem.data.account.converter.createWalletAccountDTO +import com.tangem.data.account.store.AccountsResponseStore +import com.tangem.data.account.store.AccountsResponseStoreFactory +import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration +import com.tangem.data.account.utils.toUserTokensResponse +import com.tangem.data.common.account.WalletAccountsSaver +import com.tangem.data.common.currency.UserTokensSaver +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.account.DerivationIndex +import com.tangem.domain.models.wallet.UserWalletId +import io.mockk.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@Suppress("UnusedFlow") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class DefaultMainAccountTokensMigrationTest { + + private val accountsResponseStoreFactory = mockk() + private val accountsResponseStore = mockk() + private val accountsResponseStoreFlow = MutableStateFlow(value = null) + + private val userTokensSaver = mockk(relaxed = true) + private val walletAccountsSaver = mockk(relaxed = true) + private val migration = DefaultMainAccountTokensMigration( + accountsResponseStoreFactory = accountsResponseStoreFactory, + userTokensSaver = userTokensSaver, + walletAccountsSaver = walletAccountsSaver, + ) + + private val userWalletId = UserWalletId("011") + private val derivationIndex = DerivationIndex(1).getOrNull()!! + + @BeforeEach + fun setupAll() { + every { accountsResponseStoreFactory.create(userWalletId) } returns accountsResponseStore + every { accountsResponseStore.data } returns accountsResponseStoreFlow + } + + @AfterEach + fun tearDown() { + clearMocks(accountsResponseStoreFactory, accountsResponseStore, userTokensSaver, walletAccountsSaver) + accountsResponseStoreFlow.value = null + } + + @Test + fun `migrate skips when derivation index is Main`() = runTest { + // Act + val actual = migration.migrate(userWalletId, DerivationIndex.Main) + + // Assert + assertEitherRight(actual) + + coVerify(inverse = true) { + accountsResponseStoreFactory.create(any()) + accountsResponseStore.data + walletAccountsSaver.store(userWalletId = any(), response = any()) + userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any()) + } + } + + @Test + fun `migrate fails when no cached accounts response`() = runTest { + // Act + val actual = migration.migrate(userWalletId, derivationIndex) + + // Assert + val expected = IllegalStateException("No cached accounts response found") + assertEitherLeft(actual, expected) + + coVerifySequence { + accountsResponseStoreFactory.create(userWalletId) + accountsResponseStore.data + } + + coVerify(inverse = true) { + walletAccountsSaver.store(userWalletId = any(), response = any()) + userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any()) + } + } + + @Test + fun `migrate fails when selected account DTO not found`() = runTest { + // Arrange + val response = createGetWalletAccountsResponse( + userWalletId = userWalletId, + tokens = listOf( + createBitcoin(accountIndex = DerivationIndex.Main.value), + ), + ) + + accountsResponseStoreFlow.value = response + + // Act + val actual = migration.migrate(userWalletId, derivationIndex) + + // Assert + val expected = IllegalStateException("No account found with derivation index: $derivationIndex") + assertEitherLeft(actual, expected) + + coVerifySequence { + accountsResponseStoreFactory.create(userWalletId) + accountsResponseStore.data + } + + coVerify(inverse = true) { + walletAccountsSaver.store(userWalletId = any(), response = any()) + userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any()) + } + } + + @Test + fun `migrate skips when no unassigned tokens`() = runTest { + // Arrange + val response = createGetWalletAccountsResponse( + userWalletId = userWalletId, + tokens = listOf( + createBitcoin(accountIndex = DerivationIndex.Main.value), + ), + ) + + val selectedAccount = createWalletAccountDTO( + userWalletId = userWalletId, + accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex).value, + derivationIndex = derivationIndex.value, + tokens = emptyList(), + ) + + accountsResponseStoreFlow.value = response.copy(accounts = response.accounts + selectedAccount) + + // Act + val actual = migration.migrate(userWalletId, derivationIndex) + + // Assert + assertEitherRight(actual) + + coVerifySequence { + accountsResponseStoreFactory.create(userWalletId) + accountsResponseStore.data + } + + coVerify(inverse = true) { + walletAccountsSaver.store(userWalletId = any(), response = any()) + userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any()) + } + } + + @Test + fun `migrate updates tokens for selected account`() = runTest { + // Arrange + val unassignedToken = createBitcoin(accountIndex = 1) + + val mainAccount = createWalletAccountDTO( + userWalletId = userWalletId, + accountId = AccountId.forCryptoPortfolio(userWalletId, DerivationIndex.Main).value, + derivationIndex = DerivationIndex.Main.value, + tokens = listOf( + createBitcoin(accountIndex = 0), + unassignedToken, + ), + ) + + val selectedAccount = createWalletAccountDTO( + userWalletId = userWalletId, + accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex).value, + derivationIndex = derivationIndex.value, + tokens = emptyList(), + ) + + val response = GetWalletAccountsResponse( + wallet = GetWalletAccountsResponse.Wallet( + group = UserTokensResponse.GroupType.NONE, + sort = UserTokensResponse.SortType.MANUAL, + totalAccounts = 2, + ), + accounts = listOf(mainAccount, selectedAccount), + unassignedTokens = emptyList(), + ) + + accountsResponseStoreFlow.value = response + + // Act + val actual = migration.migrate(userWalletId, derivationIndex) + + // Assert + assertEitherRight(actual) + + val migratedResponse = response.copy( + accounts = listOf( + mainAccount.copy(tokens = mainAccount.tokens!! - unassignedToken), + selectedAccount.copy(tokens = listOf(unassignedToken)), + ), + ) + + coVerifySequence { + accountsResponseStoreFactory.create(userWalletId) + accountsResponseStore.data + walletAccountsSaver.store(userWalletId = userWalletId, response = migratedResponse) + userTokensSaver.push( + userWalletId = userWalletId, + response = migratedResponse.toUserTokensResponse(), + onFailSend = any(), + ) + } + } + + private fun createBitcoin(accountIndex: Int): UserTokensResponse.Token { + return UserTokensResponse.Token( + id = "ne", + accountId = AccountId.forCryptoPortfolio( + userWalletId = userWalletId, + derivationIndex = DerivationIndex(accountIndex).getOrNull()!!, + ).value, + networkId = "bitcoin", + derivationPath = "m/44'/60'/$accountIndex'/0/0", + name = "Phil Hinton", + symbol = "graeci", + decimals = 6487, + contractAddress = "vim", + addresses = listOf(), + ) + } +} \ No newline at end of file diff --git a/data/blockaid/build.gradle.kts b/data/blockaid/build.gradle.kts index 344e76b061..8c28cf6afc 100644 --- a/data/blockaid/build.gradle.kts +++ b/data/blockaid/build.gradle.kts @@ -12,6 +12,7 @@ android { dependencies { /* Project - Domain */ implementation(projects.data.common) + implementation(projects.domain.models) implementation(projects.domain.blockaid) implementation(projects.domain.blockaid.models) @@ -20,6 +21,7 @@ dependencies { /* Project - Core */ implementation(projects.core.utils) + implementation(projects.libs.blockchainSdk) /* DI */ implementation(deps.hilt.core) diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidEvmScanTransactionConverter.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidEvmScanTransactionConverter.kt new file mode 100644 index 0000000000..213e89f7f4 --- /dev/null +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidEvmScanTransactionConverter.kt @@ -0,0 +1,31 @@ +package com.tangem.data.blockaid + +import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras +import com.tangem.blockchain.common.Blockchain +import com.tangem.datasource.api.common.blockaid.models.request.BlockAidScanOptions +import com.tangem.datasource.api.common.blockaid.models.request.Data +import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionBulkScanRequest +import com.tangem.datasource.api.common.blockaid.models.response.TransactionMetadata +import com.tangem.utils.converter.Converter +import com.tangem.blockchain.common.TransactionData as SDKTransactionData + +internal class BlockAidEvmScanTransactionConverter( + private val blockchain: Blockchain, +) : Converter, EvmTransactionBulkScanRequest> { + + override fun convert(value: List): EvmTransactionBulkScanRequest { + return EvmTransactionBulkScanRequest( + chain = blockchain.getChainId().toString(), + options = listOf(BlockAidScanOptions.GasEstimation.value), + metadata = TransactionMetadata(domain = "https://tangem.com"), + data = value.map { + Data( + from = it.sourceAddress, + to = it.destinationAddress, + data = (it.extras as? EthereumTransactionExtras)?.callData?.dataHex.orEmpty(), + ) + }, + aggregated = false, + ) + } +} \ No newline at end of file diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidRepository.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidRepository.kt index 57e8db2559..0ce7a6aeea 100644 --- a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidRepository.kt +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidRepository.kt @@ -3,11 +3,19 @@ package com.tangem.data.blockaid import com.domain.blockaid.models.dapp.CheckDAppResult import com.domain.blockaid.models.dapp.DAppData import com.domain.blockaid.models.transaction.CheckTransactionResult +import com.domain.blockaid.models.transaction.GasEstimationResult import com.domain.blockaid.models.transaction.TransactionData +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.blockchain.common.TransactionData as SDKTransactionData interface BlockAidRepository { suspend fun verifyDAppDomain(data: DAppData): CheckDAppResult suspend fun verifyTransaction(data: TransactionData): CheckTransactionResult + + suspend fun getGasEstimation( + cryptoCurrency: CryptoCurrency, + transactionDataList: List, + ): GasEstimationResult } \ No newline at end of file diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidGasEstimate.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidGasEstimate.kt new file mode 100644 index 0000000000..8b8e87189f --- /dev/null +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidGasEstimate.kt @@ -0,0 +1,19 @@ +package com.tangem.data.blockaid + +import arrow.core.Either +import com.domain.blockaid.models.transaction.GasEstimationResult +import com.tangem.blockchain.common.TransactionData +import com.tangem.domain.blockaid.BlockAidGasEstimate +import com.tangem.domain.models.currency.CryptoCurrency +import javax.inject.Inject + +class DefaultBlockAidGasEstimate @Inject constructor( + private val repository: BlockAidRepository, +) : BlockAidGasEstimate { + override suspend fun getGasEstimation( + cryptoCurrency: CryptoCurrency, + transactionDataList: List, + ): Either = Either.catch { + repository.getGasEstimation(cryptoCurrency = cryptoCurrency, transactionDataList = transactionDataList) + } +} \ No newline at end of file diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt index 985e8d7e2e..59cf796d98 100644 --- a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/DefaultBlockAidRepository.kt @@ -3,12 +3,18 @@ package com.tangem.data.blockaid import com.domain.blockaid.models.dapp.CheckDAppResult import com.domain.blockaid.models.dapp.DAppData import com.domain.blockaid.models.transaction.CheckTransactionResult +import com.domain.blockaid.models.transaction.GasEstimationResult import com.domain.blockaid.models.transaction.TransactionData import com.domain.blockaid.models.transaction.TransactionParams +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.toBlockchain +import com.tangem.data.blockaid.converters.GasEstimationResponseConverter import com.tangem.datasource.api.common.blockaid.BlockAidApi import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext +import com.tangem.blockchain.common.TransactionData as SDKTransactionData internal class DefaultBlockAidRepository( private val api: BlockAidApi, @@ -30,6 +36,17 @@ internal class DefaultBlockAidRepository( } } + override suspend fun getGasEstimation( + cryptoCurrency: CryptoCurrency, + transactionDataList: List, + ): GasEstimationResult { + val blockchain = cryptoCurrency.network.toBlockchain() + return when { + blockchain.isEvm() -> scanEvmTransactionBulk(blockchain, transactionDataList) + else -> error("Gas estimation with BlockAid not supported by ${blockchain.fullName}") + } + } + private suspend fun scanEvmTransaction(data: TransactionData): CheckTransactionResult = withContext(dispatchers.io) { val response = api.scanJsonRpc(mapper.mapToEvmRequest(data)) @@ -41,4 +58,15 @@ internal class DefaultBlockAidRepository( val response = api.scanSolanaMessage(mapper.mapToSolanaRequest(data)) mapper.mapToDomain(response) } + + private suspend fun scanEvmTransactionBulk( + blockchain: Blockchain, + transactionDataList: List, + ): GasEstimationResult = withContext(dispatchers.io) { + val response = api.scanEvmTransactionBulk( + BlockAidEvmScanTransactionConverter(blockchain).convert(transactionDataList), + ) + + GasEstimationResponseConverter.convert(response) + } } \ No newline at end of file diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/converters/GasEstimationResponseConverter.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/converters/GasEstimationResponseConverter.kt new file mode 100644 index 0000000000..51a9e25b75 --- /dev/null +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/converters/GasEstimationResponseConverter.kt @@ -0,0 +1,14 @@ +package com.tangem.data.blockaid.converters + +import com.domain.blockaid.models.transaction.GasEstimationResult +import com.tangem.blockchain.extensions.hexToBigInteger +import com.tangem.datasource.api.common.blockaid.models.response.GasEstimationResponse +import com.tangem.utils.converter.Converter + +internal object GasEstimationResponseConverter : Converter, GasEstimationResult> { + override fun convert(value: List): GasEstimationResult { + return GasEstimationResult( + estimatedGasList = value.map { it.gasEstimation.estimate.hexToBigInteger() }, + ) + } +} \ No newline at end of file diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/di/BlockAidDataModule.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/di/BlockAidDataModule.kt index 1efc366513..c61a853d1d 100644 --- a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/di/BlockAidDataModule.kt +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/di/BlockAidDataModule.kt @@ -1,6 +1,8 @@ package com.tangem.data.blockaid.di +import com.tangem.data.blockaid.DefaultBlockAidGasEstimate import com.tangem.data.blockaid.DefaultBlockAidVerifier +import com.tangem.domain.blockaid.BlockAidGasEstimate import com.tangem.domain.blockaid.BlockAidVerifier import dagger.Binds import dagger.Module @@ -15,4 +17,8 @@ interface BlockAidDataModule { @Binds @Singleton fun bindVerifier(verifier: DefaultBlockAidVerifier): BlockAidVerifier + + @Binds + @Singleton + fun bindBlockAidGasEstimate(gasEstimate: DefaultBlockAidGasEstimate): BlockAidGasEstimate } \ 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/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt index 54cb04d95b..10bb463509 100644 --- a/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt +++ b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt @@ -7,7 +7,7 @@ import com.tangem.data.feedback.converters.BlockchainInfoConverter import com.tangem.data.feedback.converters.WalletMetaInfoConverter import com.tangem.datasource.local.logs.AppLogsStore import com.tangem.datasource.local.walletmanager.WalletManagersStore -import com.tangem.domain.core.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.feedback.models.* import com.tangem.domain.feedback.repository.FeedbackRepository import com.tangem.domain.models.scan.ScanResponse diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackModule.kt b/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackModule.kt index 067feab7a8..63f1b43180 100644 --- a/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackModule.kt +++ b/data/feedback/src/main/java/com/tangem/data/feedback/di/FeedbackModule.kt @@ -6,7 +6,7 @@ import com.tangem.data.feedback.DefaultFeedbackFeatureToggles import com.tangem.data.feedback.DefaultFeedbackRepository import com.tangem.datasource.local.logs.AppLogsStore import com.tangem.datasource.local.walletmanager.WalletManagersStore -import com.tangem.domain.core.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.feedback.repository.FeedbackFeatureToggles import com.tangem.domain.feedback.repository.FeedbackRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager 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..343ba4d4bd 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 { @@ -95,7 +96,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( val mappedProviders = pair.providers.mapNotNull { mappedProviders[it.providerId] - } + }.filterYieldSupplyProvider(statusFrom) if (statusFrom != null && statusTo != null && mappedProviders.isNotEmpty()) { SwapPairModel( @@ -152,7 +153,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( val mappedProvider = pair.providers.mapNotNull { mappedProviders[it.providerId] - } + }.filterYieldSupplyProvider(currencyStatusFrom) if (currencyStatusFrom != null && currencyStatusTo != null && mappedProvider.isNotEmpty()) { SwapPairModel( @@ -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? { @@ -419,4 +422,15 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( is CryptoCurrency.Coin -> "0" } } + + private fun List.filterYieldSupplyProvider(cryptoCurrencyStatus: CryptoCurrencyStatus?) = + filter { provider -> + // !!!WARNING!!! Filter out dex provider if yield supply is active + val yieldSupplyStatus = cryptoCurrencyStatus?.value?.yieldSupplyStatus + if (yieldSupplyStatus != null && yieldSupplyStatus.isActive) { + provider.type == ExpressProviderType.CEX + } else { + true + } + } } \ No newline at end of file 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/build.gradle.kts b/data/tokens/build.gradle.kts index f35bd35737..eb3fe93e66 100644 --- a/data/tokens/build.gradle.kts +++ b/data/tokens/build.gradle.kts @@ -24,17 +24,18 @@ dependencies { // endregion // region Project - Domain + implementation(projects.domain.account) + implementation(projects.domain.card) implementation(projects.domain.core) implementation(projects.domain.demo) implementation(projects.domain.legacy) - implementation(projects.domain.walletManager) - implementation(projects.domain.card) implementation(projects.domain.models) implementation(projects.domain.staking) implementation(projects.domain.staking.models) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) implementation(projects.domain.txhistory.models) + implementation(projects.domain.walletManager) implementation(projects.domain.wallets.models) // endregion diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcher.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcher.kt new file mode 100644 index 0000000000..5c8f9d9f23 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcher.kt @@ -0,0 +1,35 @@ +package com.tangem.data.tokens + +import arrow.core.Either +import com.tangem.data.common.account.WalletAccountsFetcher +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.core.utils.catchOn +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.isMultiCurrency +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher.Params +import com.tangem.utils.coroutines.CoroutineDispatcherProvider + +/** + * Implementation of [MultiWalletCryptoCurrenciesFetcher] that fetches crypto currencies of all accounts + * + * @property userWalletsStore [UserWallet]'s store + * @property walletAccountsFetcher instance of [WalletAccountsFetcher] to fetch accounts for a multi wallet + * @property dispatchers dispatchers + * +[REDACTED_AUTHOR] + */ +internal class AccountListCryptoCurrenciesFetcher( + private val userWalletsStore: UserWalletsStore, + private val walletAccountsFetcher: WalletAccountsFetcher, + private val dispatchers: CoroutineDispatcherProvider, +) : MultiWalletCryptoCurrenciesFetcher { + + override suspend fun invoke(params: Params) = Either.catchOn(dispatchers.default) { + val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) + + if (!userWallet.isMultiCurrency) error("${this::class.simpleName} supports only multi-currency wallet") + + walletAccountsFetcher.fetch(userWalletId = params.userWalletId) + } +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt index eea90a597e..c360054a04 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt @@ -1,13 +1,16 @@ package com.tangem.data.tokens.di +import com.tangem.data.common.account.WalletAccountsFetcher import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensSaver +import com.tangem.data.tokens.AccountListCryptoCurrenciesFetcher import com.tangem.data.tokens.DefaultMultiWalletCryptoCurrenciesFetcher import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -24,28 +27,38 @@ internal class MultiWalletCryptoCurrenciesFetcherModule { @Singleton @Provides fun provideMultiWalletCryptoCurrenciesFetcher( + accountsFeatureToggles: AccountsFeatureToggles, tangemTechApi: TangemTechApi, userWalletsStore: UserWalletsStore, userTokensResponseStore: UserTokensResponseStore, userTokensSaver: UserTokensSaver, cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, expressServiceLoader: ExpressServiceLoader, + walletAccountsFetcher: WalletAccountsFetcher, dispatchers: CoroutineDispatcherProvider, ): MultiWalletCryptoCurrenciesFetcher { - return DefaultMultiWalletCryptoCurrenciesFetcher( - demoConfig = DemoConfig(), - userWalletsStore = userWalletsStore, - tangemTechApi = tangemTechApi, - customTokensMerger = CustomTokensMerger( - tangemTechApi = tangemTechApi, - userTokensSaver = userTokensSaver, + return if (accountsFeatureToggles.isFeatureEnabled) { + AccountListCryptoCurrenciesFetcher( + userWalletsStore = userWalletsStore, + walletAccountsFetcher = walletAccountsFetcher, dispatchers = dispatchers, - ), - userTokensResponseStore = userTokensResponseStore, - userTokensSaver = userTokensSaver, - cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, - expressServiceLoader = expressServiceLoader, - dispatchers = dispatchers, - ) + ) + } else { + DefaultMultiWalletCryptoCurrenciesFetcher( + demoConfig = DemoConfig(), + userWalletsStore = userWalletsStore, + tangemTechApi = tangemTechApi, + customTokensMerger = CustomTokensMerger( + tangemTechApi = tangemTechApi, + userTokensSaver = userTokensSaver, + dispatchers = dispatchers, + ), + userTokensResponseStore = userTokensResponseStore, + userTokensSaver = userTokensSaver, + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, + expressServiceLoader = expressServiceLoader, + dispatchers = dispatchers, + ) + } } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 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/tokens/src/test/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcherTest.kt b/data/tokens/src/test/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcherTest.kt new file mode 100644 index 0000000000..79bcd8e6f8 --- /dev/null +++ b/data/tokens/src/test/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcherTest.kt @@ -0,0 +1,103 @@ +package com.tangem.data.tokens + +import arrow.core.left +import com.tangem.common.test.utils.assertEither +import com.tangem.common.test.utils.assertEitherRight +import com.tangem.data.common.account.WalletAccountsFetcher +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.isMultiCurrency +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class AccountListCryptoCurrenciesFetcherTest { + + private val userWalletsStore: UserWalletsStore = mockk(relaxUnitFun = true) + private val walletAccountsFetcher: WalletAccountsFetcher = mockk(relaxUnitFun = true) + private val dispatchers = TestingCoroutineDispatcherProvider() + + private val fetcher = AccountListCryptoCurrenciesFetcher( + userWalletsStore = userWalletsStore, + walletAccountsFetcher = walletAccountsFetcher, + dispatchers = dispatchers, + ) + + @BeforeEach + fun resetMocks() { + clearMocks(userWalletsStore, walletAccountsFetcher) + } + + @Test + fun `returns failure if wallet is not multi-currency`() = runTest { + // Arrange + val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId) + val mockUserWallet = mockk { every { isMultiCurrency } returns false } + every { userWalletsStore.getSyncStrict(key = params.userWalletId) } returns mockUserWallet + + // Act + val actual = fetcher(params) + + // Assert + val expected = IllegalStateException( + "${AccountListCryptoCurrenciesFetcher::class.simpleName} supports only multi-currency wallet", + ).left() + assertEither(actual, expected) + + verify { userWalletsStore.getSyncStrict(key = params.userWalletId) } + coVerify(inverse = true) { walletAccountsFetcher.fetch(any()) } + } + + @Test + fun `returns accounts if wallet is multi-currency`() = runTest { + // Arrange + val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId) + val mockUserWallet = mockk { every { isMultiCurrency } returns true } + + every { userWalletsStore.getSyncStrict(key = params.userWalletId) } returns mockUserWallet + + // Act + val actual = fetcher(params) + + // Assert + assertEitherRight(actual) + + coVerify(ordering = Ordering.SEQUENCE) { + userWalletsStore.getSyncStrict(key = params.userWalletId) + walletAccountsFetcher.fetch(userWalletId = params.userWalletId) + } + } + + @Test + fun `returns error if walletAccountsFetcher returns error`() = runTest { + // Arrange + val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId) + val mockUserWallet = mockk { every { isMultiCurrency } returns true } + val error = RuntimeException("fetch error") + + every { userWalletsStore.getSyncStrict(key = params.userWalletId) } returns mockUserWallet + coEvery { walletAccountsFetcher.fetch(userWalletId = params.userWalletId) } throws error + + // Act + val actual = fetcher(params) + + // Assert + val expected = error.left() + assertEither(actual, expected) + + coVerify(ordering = Ordering.SEQUENCE) { + userWalletsStore.getSyncStrict(key = params.userWalletId) + walletAccountsFetcher.fetch(userWalletId = params.userWalletId) + } + } + + private companion object { + val userWalletId = UserWalletId("012") + } +} \ No newline at end of file diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultFeeRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultFeeRepository.kt index 49b6e3c486..07898f241d 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultFeeRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultFeeRepository.kt @@ -1,7 +1,13 @@ package com.tangem.data.transaction +import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager +import com.tangem.blockchain.blockchains.ethereum.eip1559.isSupportEIP1559 +import com.tangem.blockchain.blockchains.ethereum.network.EthereumFeeHistory +import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.blockchainsdk.utils.toBlockchain @@ -12,6 +18,8 @@ import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.walletmanager.WalletManagersFacade +import java.math.BigDecimal +import java.math.BigInteger internal class DefaultFeeRepository( private val walletManagersFacade: WalletManagersFacade, @@ -22,6 +30,53 @@ internal class DefaultFeeRepository( return networkId.toBlockchain().isFeeApproximate(amountType) } + override suspend fun getEthereumFeeWithoutGas( + userWallet: UserWallet, + cryptoCurrency: CryptoCurrency, + ): Fee.Ethereum { + val walletManager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWallet.walletId, + network = cryptoCurrency.network, + ) + + val blockchain = cryptoCurrency.network.toBlockchain() + + val ethereumWalletManager = walletManager as? EthereumWalletManager + ?: error("Not supported for ${cryptoCurrency.network}") + + val fee = if (blockchain.isSupportEIP1559) { + val gasHistory = when (val gasHistory = ethereumWalletManager.getGasHistory()) { + is Result.Failure -> throw gasHistory.error + is Result.Success -> gasHistory.data + } + + val marketPriorityFee = when (gasHistory) { + is EthereumFeeHistory.Common -> gasHistory.marketPriorityFee + is EthereumFeeHistory.Fallback -> gasHistory.gasPrice.toBigDecimal() * MULTIPLIER_GAS_PRICE_NORMAL_FEE + } + + val maxFeePerGas = gasHistory.baseFee * MULTIPLIER_GAS_PRICE_NORMAL_FEE + marketPriorityFee + + getEthEip1559Fee( + maxFeePerGas = maxFeePerGas.toBigInteger(), + priorityFee = marketPriorityFee.toBigInteger(), + blockchain = blockchain, + ) + } else { + val gasPrice = when (val gasPrice = ethereumWalletManager.getGasPrice()) { + is Result.Failure -> throw gasPrice.error + is Result.Success -> gasPrice.data + } + + getEthLegacyFee( + gasPrice = gasPrice, + blockchain = blockchain, + ) + } + + return fee + } + override suspend fun calculateFee( userWallet: UserWallet, cryptoCurrency: CryptoCurrency, @@ -54,4 +109,37 @@ internal class DefaultFeeRepository( ?: error("WalletManager is null"), ) } + + private fun getEthLegacyFee(gasPrice: BigInteger, blockchain: Blockchain): Fee.Ethereum.Legacy { + val amount = Amount( + value = BigDecimal.ZERO, + blockchain = blockchain, + ) + return Fee.Ethereum.Legacy( + amount = amount, + gasLimit = BigInteger.ZERO, + gasPrice = gasPrice, + ) + } + + private fun getEthEip1559Fee( + maxFeePerGas: BigInteger, + priorityFee: BigInteger, + blockchain: Blockchain, + ): Fee.Ethereum.EIP1559 { + val amount = Amount( + value = BigDecimal.ZERO, + blockchain = blockchain, + ) + return Fee.Ethereum.EIP1559( + amount = amount, + gasLimit = BigInteger.ZERO, + maxFeePerGas = maxFeePerGas, + priorityFee = priorityFee, + ) + } + + private companion object { + val MULTIPLIER_GAS_PRICE_NORMAL_FEE = "1.2".toBigDecimal() // 120% + } } \ No newline at end of file diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 437fbe7c78..8054cbd5c0 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -57,6 +57,17 @@ internal class DefaultTransactionRepository( val extras = txExtras ?: getMemoExtras(networkId = network.rawId, memo) + val destination = if (amount.type is AmountType.TokenYieldSupply) { + walletManager.getYieldModuleAddress() + } else { + destination + } + val amount = if (amount.type is AmountType.TokenYieldSupply) { + amount.copy(value = BigDecimal.ZERO) + } else { + amount + } + return@withContext if (fee != null) { walletManager.createTransaction( amount = amount, @@ -86,11 +97,6 @@ internal class DefaultTransactionRepository( nonce: BigInteger?, ): TransactionData.Uncompiled = withContext(dispatchers.io) { val blockchain = network.toBlockchain() - val walletManager = walletManagersFacade.getOrCreateWalletManager( - userWalletId = userWalletId, - blockchain = blockchain, - derivationPath = network.derivationPath.value, - ) ?: error("Wallet manager not found") val callData = SmartContractCallDataProviderFactory.getTokenTransferCallData( destinationAddress = destination, @@ -98,7 +104,7 @@ internal class DefaultTransactionRepository( blockchain = blockchain, ) - val extras = if (amount.type is AmountType.Token && callData != null) { + val extras = if (callData != null) { createTransactionDataExtras( callData = callData, network = network, @@ -109,25 +115,15 @@ internal class DefaultTransactionRepository( null } - return@withContext if (fee != null) { - createTransaction( - amount = amount, - fee = fee, - memo = null, - destination = destination, - userWalletId = userWalletId, - network = network, - txExtras = getMemoExtras(networkId = network.rawId, memo = memo) ?: extras, - ) - } else { - TransactionData.Uncompiled( - amount = amount, - sourceAddress = walletManager.wallet.address, - destinationAddress = destination, - extras = getMemoExtras(networkId = network.rawId, memo = memo) ?: extras, - fee = null, - ) - } + createTransaction( + amount = amount, + fee = fee, + memo = null, + destination = destination, + userWalletId = userWalletId, + network = network, + txExtras = getMemoExtras(networkId = network.rawId, memo = memo) ?: extras, + ) } override suspend fun createApprovalTransaction( diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index 05adcdc660..ae8f40e9b4 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -28,6 +28,10 @@ dependencies { implementation(projects.domain.appCurrency.models) implementation(projects.domain.tokens.models) implementation(projects.domain.tokens) + implementation(projects.domain.networks) + + /** Feature API - remove after removing [HotWalletFeatureToggles] */ + implementation(projects.features.hotWallet.api) /** Project - Utils */ implementation(projects.core.utils) 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..30b4c347fa 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,16 @@ 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.TangemPayIssueOrderUseCase +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 +26,24 @@ 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) + } + + @Provides + @Singleton + fun provideTangemPayIssueOrderUseCase(repository: OnboardingRepository): TangemPayIssueOrderUseCase { + return TangemPayIssueOrderUseCase(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..e7c91d654c 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,24 +1,26 @@ package com.tangem.data.pay.repository -import com.tangem.datasource.api.common.response.getOrThrow +import arrow.core.Either +import com.tangem.core.error.UniversalError import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.domain.pay.KycStartInfo import com.tangem.domain.pay.repository.KycRepository -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext import javax.inject.Inject +private const val TAG = "TangemPay: KycRepository" + internal class DefaultKycRepository @Inject constructor( private val tangemPayApi: TangemPayApi, - private val dispatchers: CoroutineDispatcherProvider, private val requestHelper: TangemPayRequestPerformer, ) : KycRepository { - override suspend fun getKycStartInfo() = withContext(dispatchers.io) { - requestHelper.request { authHeader -> - tangemPayApi.getKycAccess(authHeader = authHeader).getOrThrow().result - }.map { - KycStartInfo(token = it.token, locale = it.locale) + override suspend fun getKycStartInfo(): Either { + return requestHelper.runWithErrorLogs(TAG) { + val result = requestHelper.request { authHeader -> + tangemPayApi.getKycAccess(authHeader = authHeader) + }.result + + KycStartInfo(token = result.token, locale = 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..450444bf67 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 @@ -1,40 +1,108 @@ 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.request.OrderRequest +import com.tangem.datasource.api.pay.models.response.CustomerMeResponse +import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.pay.model.CustomerInfo -import com.tangem.domain.pay.model.ProductInstance +import com.tangem.domain.pay.model.CustomerInfo.CardInfo +import com.tangem.domain.pay.model.CustomerInfo.ProductInstance +import com.tangem.domain.pay.model.MainScreenCustomerInfo +import com.tangem.domain.pay.model.OrderStatus import com.tangem.domain.pay.repository.OnboardingRepository -import com.tangem.domain.visa.error.VisaApiError import javax.inject.Inject private const val VALID_STATUS = "valid" +private const val TAG = "TangemPay: OnboardingRepository" internal class DefaultOnboardingRepository @Inject constructor( private val tangemPayApi: TangemPayApi, private val requestHelper: TangemPayRequestPerformer, + private val tangemPayStorage: TangemPayStorage, ) : OnboardingRepository { - 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 } + override suspend fun validateDeeplink(link: String): Either { + return requestHelper.runWithErrorLogs(TAG) { + val result = requestHelper.request { + tangemPayApi.validateDeeplink(DeeplinkValidityRequest(link)) + }.result + 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, + override suspend fun getCustomerInfo(): Either { + return requestHelper.runWithErrorLogs(TAG) { + val result = requestHelper.request { authHeader -> + tangemPayApi.getCustomerMe(authHeader) + }.result + getCustomerInfo(result) + } + } + + override suspend fun getMainScreenCustomerInfo(): Either { + return requestHelper.runWithErrorLogs(TAG) { + val result = requestHelper.requestWithPersistedToken { authHeader -> + tangemPayApi.getCustomerMe(authHeader) + }.result + + val orderStatus = getOrderStatus().getOrNull() ?: error("Order status is null") + + MainScreenCustomerInfo(info = getCustomerInfo(result), orderStatus = orderStatus) + } + } + + override suspend fun createOrder(): Either { + return requestHelper.runWithErrorLogs(TAG) { + val walletAddress = requestHelper.getCustomerWalletAddress() + val result = requestHelper.requestWithPersistedToken { authHeader -> + tangemPayApi.createOrder(authHeader, body = OrderRequest(walletAddress)) + }.result ?: error("Create order result is null") + + tangemPayStorage.storeOrderId(result.data.customerWalletAddress, result.id) + } + } + + private fun getCustomerInfo(response: CustomerMeResponse.Result?): CustomerInfo { + val card = response?.card + val balance = response?.balance + val productInstance = response?.productInstance + val cardInfo = if (productInstance != null && card != null && balance != null) { + CardInfo( + lastFourDigits = card.cardNumberEnd, + balance = balance.availableBalance, + currencyCode = balance.currency, + customerWalletAddress = productInstance.cardWalletAddress, + ) + } else { + null + } + return CustomerInfo( + productInstance = response?.productInstance?.let { ProductInstance(id = it.id, status = it.status) }, + kycStatus = response?.kyc?.status, + cardInfo = cardInfo, + ) + } + + private suspend fun getOrderStatus(): Either { + return requestHelper.runWithErrorLogs(TAG) { + val walletAddress = requestHelper.getCustomerWalletAddress() + val orderId: String = tangemPayStorage.getOrderId(walletAddress) + ?: return@runWithErrorLogs OrderStatus.NOT_ISSUED + + val result = requestHelper.request { authHeader -> + tangemPayApi.getOrder(authHeader, orderId) + }.result ?: error("Order result is null") + + when (result.status) { + OrderStatus.NEW.apiName -> OrderStatus.NEW + OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING + OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED + else -> OrderStatus.CANCELED + } } } } \ 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..1498a36a01 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt @@ -0,0 +1,91 @@ +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.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" +private const val TAG = "TangemPay: TangemPayTxHistoryRepository:" + +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(customerWalletAddress = config.customerWalletAddress, cursor = cursor), + skipCache = config.refresh, + block = { fetch(customerWalletAddress = config.customerWalletAddress, cursor = cursor, pageSize = limit) }, + ) + + return txHistoryItemsStore.getSyncOrNull( + key = config.customerWalletAddress, + cursor = cursor ?: INITIAL_CURSOR, + ).orEmpty() + } + + private fun getCacheKey(customerWalletAddress: String, cursor: String?): String { + return "tangem_pay_tx_history_${customerWalletAddress}_${cursor ?: INITIAL_CURSOR}" + } + + private suspend fun fetch(customerWalletAddress: String, cursor: String?, pageSize: Int) { + requestPerformer.runWithErrorLogs(TAG) { + val result = requestPerformer.request { authHeader -> + visaApi.getTangemPayTxHistory(authHeader = authHeader, limit = pageSize, cursor = cursor) + }.result + val items = TangemPayTxHistoryItemConverter.convertList(result.transactions) + txHistoryItemsStore.store(key = customerWalletAddress, 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..dffb6ede24 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 @@ -1,80 +1,110 @@ package com.tangem.data.pay.repository import arrow.core.Either -import arrow.core.raise.either import com.squareup.moshi.Moshi +import com.tangem.blockchain.common.Blockchain 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.common.wallets.UserWalletsListRepository +import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.networks.repository.NetworksRepository 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.derivations.derivationStyleProvider +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.features.hotwallet.HotWalletFeatureToggles 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.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext +import timber.log.Timber +import javax.inject.Inject -/** - * For TangemPay Customer Wallet auth we are using polygon address - */ -private const val POL_VALUE = "coin⟨POLYGON⟩polygon-ecosystem-token" - +@Suppress("LongParameterList") 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 networksRepository: NetworksRepository, private val authDataSource: TangemPayAuthDataSource, + private val userWalletsListManager: UserWalletsListManager, + private val userWalletsListRepository: UserWalletsListRepository, + private val hotWalletFeatureToggles: HotWalletFeatureToggles, ) { - private val visaErrorAdapter = VisaErrorResponseJsonAdapter(moshi) private var customerWalletAddress: String? = null private val refreshTokensMutex = Mutex() - private var refreshTokensJob: Deferred>? = null + private var refreshTokensJob: Deferred? = null - suspend fun request(requestBlock: suspend (header: String) -> T): Either = either { - withContext(dispatchers.io) { - performRequest(requestBlock = requestBlock, refreshTokens = ::refreshAuthTokens).bind() + private val visaErrorAdapter = VisaErrorResponseJsonAdapter(moshi) + + suspend fun runWithErrorLogs(tag: String, requestBlock: suspend () -> T): Either { + return try { + val result = requestBlock() + Either.Right(result) + } catch (exception: Exception) { + Timber.e("$tag: $exception") + Either.Left(mapError(exception)) } } + suspend fun request(requestBlock: suspend (header: String) -> ApiResponse): T = + withContext(dispatchers.io) { + performRequest( + requestBlock = requestBlock, + getTokens = ::getAccessTokens, + refreshTokens = ::refreshAuthTokens, + ) + } + + suspend fun requestWithPersistedToken(requestBlock: suspend (header: String) -> ApiResponse): T = + withContext(dispatchers.io) { + performRequest( + requestBlock = requestBlock, + getTokens = { getAccessTokensIfSaved() ?: error("Cannot get saved access tokens") }, + refreshTokens = ::refreshAuthTokens, + ) + } + + private fun getWallets(): Flow> = if (hotWalletFeatureToggles.isHotWalletEnabled) { + userWalletsListRepository.userWallets.map { requireNotNull(it) } + } else { + userWalletsListManager.userWallets + } + private suspend fun performRequest( - requestBlock: suspend (header: String) -> T, - refreshTokens: (suspend () -> Either)? = null, - ): Either = either { - runCatching { - requestBlock("Bearer ${getAccessTokens().bind().accessToken}") - }.getOrElse { error -> - when (error) { - is ApiResponseError.HttpException -> { - if (refreshTokens != null && error.code == ApiResponseError.HttpException.Code.UNAUTHORIZED) { - refreshOrJoin(refreshTokens).bind() - performRequest(requestBlock, refreshTokens = null).bind() - } else { - raise(mapHttpError(error)) - } - } - else -> raise(VisaApiError.UnknownWithoutCode) - } + requestBlock: suspend (header: String) -> ApiResponse, + getTokens: (suspend () -> VisaAuthTokens), + refreshTokens: (suspend () -> VisaAuthTokens)? = null, + ): T = runCatching { + requestBlock("Bearer ${getTokens().accessToken}").getOrThrow() + }.getOrElse { error -> + val unauthorizedCode = ApiResponseError.HttpException.Code.UNAUTHORIZED + if (error is ApiResponseError.HttpException && refreshTokens != null && error.code == unauthorizedCode) { + refreshOrJoin(refreshTokens) + performRequest(requestBlock, refreshTokens = null, getTokens = getTokens) + } else { + throw error } } - private suspend fun refreshOrJoin( - refreshTokens: suspend () -> Either, - ): Either { - val jobToAwait: Deferred> = + private suspend fun refreshOrJoin(refreshTokens: suspend () -> VisaAuthTokens): VisaAuthTokens { + val jobToAwait: Deferred = refreshTokensMutex.withLock { val current = refreshTokensJob if (current == null || current.isCompleted) { @@ -87,8 +117,6 @@ internal class TangemPayRequestPerformer @Inject constructor( } val result = try { jobToAwait.await() - } catch (ignore: Throwable) { - Either.Left(VisaApiError.UnknownWithoutCode) } finally { refreshTokensMutex.withLock { if (refreshTokensJob === jobToAwait && jobToAwait.isCompleted) { @@ -99,57 +127,67 @@ internal class TangemPayRequestPerformer @Inject constructor( return result } - private suspend fun getCustomerWalletAddress(): Either = either { - customerWalletAddress - ?: tangemPayStorage.getCustomerWalletAddress() - ?: fetchAuthInputData().bind().address + suspend fun getCustomerWalletAddress(): String = customerWalletAddress ?: fetchAuthInputData().address + + private suspend fun getAccessTokens(): VisaAuthTokens { + return getAccessTokensIfSaved() ?: fetchTokens() } - private suspend fun getAccessTokens(): Either = either { - val address = getCustomerWalletAddress().bind() - tangemPayStorage.getAuthTokens(address) ?: fetchTokens().bind() + private suspend fun getAccessTokensIfSaved(): VisaAuthTokens? { + return tangemPayStorage.getAuthTokens(getCustomerWalletAddress()) } - private fun mapHttpError(throwable: ApiResponseError.HttpException): UniversalError { - val errorBody = throwable.errorBody ?: return VisaApiError.UnknownWithoutCode - return runCatching { - visaErrorAdapter.fromJson(errorBody)?.error?.code ?: throwable.code.numericCode - }.map { - VisaApiError.fromBackendError(it) - }.getOrElse { - VisaApiError.UnknownWithoutCode - } - } + private suspend fun fetchAuthInputData(): AuthInputData { + val userWallets = getWallets() + .filter { it.isNotEmpty() } + .first() + val wallet = userWallets.find { it is UserWallet.Cold } as? UserWallet.Cold + ?: error("Cannot find cold user wallet") - private suspend fun fetchAuthInputData(): Either