diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 91ee07995e..1bc92535f2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -280,6 +280,8 @@ dependencies { implementation(tangemDeps.card.android) { exclude(module = "joda-time") } + implementation(tangemDeps.hot.core) + implementation(tangemDeps.hot.android) /** DI */ implementation(deps.hilt.android) @@ -299,6 +301,7 @@ dependencies { implementation(deps.zxing.qrCore) implementation(deps.coil) implementation(deps.coil.gif) + implementation(deps.coil.svg) implementation(deps.amplitude) implementation(deps.kotsonGson) implementation(deps.spongecastle.core) diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt index 48696ff92e..46e41677b6 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -1,7 +1,8 @@ package com.tangem.common import android.Manifest -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.junit4.createEmptyComposeRule +import androidx.test.core.app.ActivityScenario import androidx.test.espresso.intent.Intents import androidx.test.rule.GrantPermissionRule import com.kaspersky.components.alluresupport.interceptors.step.ScreenshotStepInterceptor @@ -10,8 +11,8 @@ import com.kaspersky.components.composesupport.config.addComposeSupport import com.kaspersky.kaspresso.kaspresso.Kaspresso import com.kaspersky.kaspresso.testcases.api.testcase.TestCase import com.tangem.common.allure.FailedStepScreenshotInterceptor -import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.sdk.api.TangemSdkManager +import com.tangem.common.rules.ApiEnvironmentRule +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager import com.tangem.tap.MainActivity import dagger.hilt.android.testing.HiltAndroidRule import org.junit.Rule @@ -35,17 +36,20 @@ abstract class BaseTestCase : TestCase( ) { @Inject - lateinit var tangemSdkManager: TangemSdkManager - - @Inject - lateinit var appPreferencesStore: AppPreferencesStore + lateinit var apiConfigsManager: ApiConfigsManager private val hiltRule = HiltAndroidRule(this) - private val permissionRule = GrantPermissionRule.grant( + private val apiEnvironmentRule = ApiEnvironmentRule() + private val permissionRule = GrantPermissionRule.grant( Manifest.permission.POST_NOTIFICATIONS, Manifest.permission.CAMERA, ) - val composeTestRule = createAndroidComposeRule() + + /** + * It is important to use `ComposeRule` without specifying an activity to ensure that the initialization order of + * all test rules is fully controlled. + */ + val composeTestRule = createEmptyComposeRule() @Rule @JvmField @@ -53,18 +57,26 @@ abstract class BaseTestCase : TestCase( .outerRule(hiltRule) .around(ApplicationInjectionExecutionRule()) .around(permissionRule) + .around(apiEnvironmentRule) .around(composeTestRule) + /** + * Initialization order is important: + * – DI dependencies must be injected first, + * – then the API environment should be set up, + * – and only after that the activity should be launched. + */ protected fun setupHooks( additionalBeforeSection: () -> Unit = {}, additionalAfterSection: () -> Unit = {}, ) = before { hiltRule.inject() + apiEnvironmentRule.setup(apiConfigsManager) + ActivityScenario.launch(MainActivity::class.java) Intents.init() additionalBeforeSection() }.after { additionalAfterSection() Intents.release() } - } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/common/annotations/ApiEnv.kt b/app/src/androidTest/kotlin/com/tangem/common/annotations/ApiEnv.kt new file mode 100644 index 0000000000..637411492b --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/annotations/ApiEnv.kt @@ -0,0 +1,14 @@ +package com.tangem.common.annotations + +import com.tangem.datasource.api.common.config.ApiEnvironment + +/** + * Annotation to specify the API environment for a class or function. + * + * @property environment the API environment to be used (defaults to [ApiEnvironment.MOCK]) + */ +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class ApiEnv( + val environment: ApiEnvironment = ApiEnvironment.MOCK, +) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/common/rules/ApiEnvironmentRule.kt b/app/src/androidTest/kotlin/com/tangem/common/rules/ApiEnvironmentRule.kt new file mode 100644 index 0000000000..156e7054c9 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/rules/ApiEnvironmentRule.kt @@ -0,0 +1,78 @@ +package com.tangem.common.rules + +import androidx.test.platform.app.InstrumentationRegistry +import com.tangem.common.annotations.ApiEnv +import com.tangem.datasource.api.common.config.ApiEnvironment +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager +import com.tangem.datasource.api.common.config.managers.MutableApiConfigsManager +import com.tangem.wallet.test.BuildConfig +import kotlinx.coroutines.runBlocking +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import timber.log.Timber + +/** + * A JUnit rule that sets up the API environment for tests based on annotations or instrumentation arguments. + * + * This rule allows tests to specify which API environment to use either through an annotation on the test method/class + * or via an instrumentation argument. + * + * This rule only finds and stores the required API environment. The actual environment setup is performed + * by calling the [setup] method with an appropriate [ApiConfigsManager] instance. + */ +class ApiEnvironmentRule : TestRule { + + private var targetEnvironment: ApiEnvironment? = null + + /** + * Sets up the API environment based on the provided [ApiConfigsManager]. + * This method is typically called in the test setup phase to ensure the correct API environment is configured. + * + * @param apiConfigsManager the manager responsible for API configurations + */ + fun setup(apiConfigsManager: ApiConfigsManager) { + val mutableManager = requireNotNull(apiConfigsManager as? MutableApiConfigsManager) { + "MutableApiConfigsManager isn't available in build type ${BuildConfig.BUILD_TYPE}." + } + + mutableManager.setupEnvironment() + } + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + targetEnvironment = determineEnvironment(description) + + base.evaluate() + } + } + } + + private fun determineEnvironment(description: Description): ApiEnvironment { + val instrumentationArgs = InstrumentationRegistry.getArguments() + val envArg = instrumentationArgs.getString(ENV_ARGUMENT) + + if (!envArg.isNullOrEmpty()) return ApiEnvironment.valueOf(envArg.uppercase()) + + val methodAnnotation = description.getAnnotation(ApiEnv::class.java) + if (methodAnnotation != null) return methodAnnotation.environment + + val classAnnotation = description.testClass.getAnnotation(ApiEnv::class.java) + if (classAnnotation != null) return classAnnotation.environment + + return ApiEnvironment.MOCK + } + + private fun MutableApiConfigsManager.setupEnvironment() { + val environment = requireNotNull(targetEnvironment) { "Target environment is null" } + + runBlocking { changeEnvironment(environment) } + + Timber.i("API environment set to: ${environment.name}") + } + + private companion object { + const val ENV_ARGUMENT = "testEnvironment" + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/OpenMainScreenScenario.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/OpenMainScreenScenario.kt index cd966f6a0f..7a2117accb 100644 --- a/app/src/androidTest/kotlin/com/tangem/scenarios/OpenMainScreenScenario.kt +++ b/app/src/androidTest/kotlin/com/tangem/scenarios/OpenMainScreenScenario.kt @@ -5,10 +5,7 @@ import com.kaspersky.kaspresso.testcases.api.scenario.Scenario import com.kaspersky.kaspresso.testcases.core.testcontext.TestContext import com.tangem.common.extensions.clickWithAssertion import com.tangem.domain.models.scan.ProductType -import com.tangem.screens.DisclaimerTestScreen -import com.tangem.screens.MainTestScreen -import com.tangem.screens.StoriesTestScreen -import com.tangem.screens.TestTopBar +import com.tangem.screens.* import com.tangem.tap.domain.sdk.mocks.MockProvider import io.github.kakaocup.compose.node.element.ComposeScreen @@ -20,22 +17,22 @@ class OpenMainScreenScenario( if (productType != null) { MockProvider.setMocks(productType) } - ComposeScreen.onComposeScreen(testRule) { + ComposeScreen.onComposeScreen(testRule) { step("Click on \"Accept\" button") { acceptButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(testRule) { + ComposeScreen.onComposeScreen(testRule) { step("Click on \"Scan\" button") { scanButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(testRule) { + ComposeScreen.onComposeScreen(testRule) { step("Make sure wallet screen is visible") { assertIsDisplayed() } } - ComposeScreen.onComposeScreen(testRule) { + ComposeScreen.onComposeScreen(testRule) { step("Close Markets tooltip"){ performClick() } diff --git a/app/src/androidTest/kotlin/com/tangem/screens/DetailsTestScreen.kt b/app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt similarity index 60% rename from app/src/androidTest/kotlin/com/tangem/screens/DetailsTestScreen.kt rename to app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt index 528989a4e3..62b73d6c4b 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/DetailsTestScreen.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt @@ -1,25 +1,27 @@ package com.tangem.screens import androidx.compose.ui.test.SemanticsNodeInteractionsProvider -import com.tangem.core.ui.test.TestTags +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.DetailsScreenTestTags 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 -class DetailsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : - ComposeScreen( +class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen( semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(TestTags.DETAILS_SCREEN) } + viewBuilderAction = { hasTestTag(DetailsScreenTestTags.SCREEN_CONTAINER) } ) { val walletConnectButton: KNode = child { - hasTestTag(TestTags.DETAILS_SCREEN_ITEM) + hasTestTag(DetailsScreenTestTags.SCREEN_ITEM) hasText(getResourceString(R.string.wallet_connect_title)) } private val walletBlock: KNode = child { - hasTestTag(TestTags.DETAILS_SCREEN_ITEM) + hasTestTag(DetailsScreenTestTags.SCREEN_ITEM) } val walletNameButton: KNode = walletBlock.child { @@ -32,20 +34,23 @@ class DetailsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : } val buyTangemButton: KNode = child { - hasTestTag(TestTags.DETAILS_SCREEN_ITEM) + hasTestTag(DetailsScreenTestTags.SCREEN_ITEM) hasText(getResourceString(R.string.details_buy_wallet)) } val appSettingsButton: KNode = child { - hasTestTag(TestTags.DETAILS_SCREEN_ITEM) + hasTestTag(DetailsScreenTestTags.SCREEN_ITEM) hasText(getResourceString(R.string.app_settings_title)) } val contactSupportButton: KNode = child { - hasTestTag(TestTags.DETAILS_SCREEN_ITEM) + hasTestTag(DetailsScreenTestTags.SCREEN_ITEM) hasText(getResourceString(R.string.details_row_title_contact_to_support)) } val toSButton: KNode = child { - hasTestTag(TestTags.DETAILS_SCREEN_ITEM) + hasTestTag(DetailsScreenTestTags.SCREEN_ITEM) hasText(getResourceString(R.string.disclaimer_title)) } -} \ No newline at end of file +} + +internal fun BaseTestCase.onDetailsScreen(function: DetailsPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/DisclaimerPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/DisclaimerPageObject.kt new file mode 100644 index 0000000000..fa12033630 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/DisclaimerPageObject.kt @@ -0,0 +1,22 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.DisclaimerScreenTestTags +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 + +class DisclaimerPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(DisclaimerScreenTestTags.SCREEN_CONTAINER) } + ) { + + val acceptButton: KNode = child { + hasTestTag(DisclaimerScreenTestTags.ACCEPT_BUTTON) + } +} + +internal fun BaseTestCase.onDisclaimerScreen(function: DisclaimerPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/DisclaimerTestScreen.kt b/app/src/androidTest/kotlin/com/tangem/screens/DisclaimerTestScreen.kt deleted file mode 100644 index 4af7f33d51..0000000000 --- a/app/src/androidTest/kotlin/com/tangem/screens/DisclaimerTestScreen.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.screens - -import androidx.compose.ui.test.SemanticsNodeInteractionsProvider -import com.tangem.core.ui.test.TestTags -import io.github.kakaocup.compose.node.element.ComposeScreen -import io.github.kakaocup.compose.node.element.KNode - -class DisclaimerTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : - ComposeScreen( - semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(TestTags.DISCLAIMER_SCREEN_CONTAINER) } - ) { - - val acceptButton: KNode = child { - hasTestTag(TestTags.DISCLAIMER_SCREEN_ACCEPT_BUTTON) - } -} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt new file mode 100644 index 0000000000..73cf124ab4 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt @@ -0,0 +1,16 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.MainScreenTestTags +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen + +class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(MainScreenTestTags.SCREEN_CONTAINER) } + ) + +internal fun BaseTestCase.onMainScreen(function: MainScreenPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/MainTestScreen.kt b/app/src/androidTest/kotlin/com/tangem/screens/MainTestScreen.kt deleted file mode 100644 index a342754478..0000000000 --- a/app/src/androidTest/kotlin/com/tangem/screens/MainTestScreen.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.screens - -import androidx.compose.ui.test.SemanticsNodeInteractionsProvider -import com.tangem.core.ui.test.TestTags -import io.github.kakaocup.compose.node.element.ComposeScreen - -class MainTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : - ComposeScreen( - semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(TestTags.MAIN_SCREEN) } - ) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/StoriesTestScreen.kt b/app/src/androidTest/kotlin/com/tangem/screens/StoriesPageObject.kt similarity index 51% rename from app/src/androidTest/kotlin/com/tangem/screens/StoriesTestScreen.kt rename to app/src/androidTest/kotlin/com/tangem/screens/StoriesPageObject.kt index 9dd1a415e9..63934bb015 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/StoriesTestScreen.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/StoriesPageObject.kt @@ -1,25 +1,27 @@ package com.tangem.screens import androidx.compose.ui.test.SemanticsNodeInteractionsProvider -import com.tangem.core.ui.test.TestTags +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 StoriesTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : - ComposeScreen( +class StoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen( semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(TestTags.STORIES_SCREEN) } + viewBuilderAction = { hasTestTag(StoriesScreenTestTags.SCREEN_CONTAINER) } ) { val scanButton: KNode = child { - hasTestTag(TestTags.STORIES_SCREEN_SCAN_BUTTON) + hasTestTag(StoriesScreenTestTags.SCAN_BUTTON) } val orderButton: KNode = child { - hasTestTag(TestTags.STORIES_SCREEN_ORDER_BUTTON) + hasTestTag(StoriesScreenTestTags.ORDER_BUTTON) } val enableNFCAlert: KView = KView { @@ -29,4 +31,7 @@ class StoriesTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : val cancelButton: KButton = KButton { withId(android.R.id.button2) } -} \ No newline at end of file +} + +internal fun BaseTestCase.onStoriesScreen(function: StoriesPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/TestTopBar.kt b/app/src/androidTest/kotlin/com/tangem/screens/TestTopBar.kt deleted file mode 100644 index 83d2e8a6a9..0000000000 --- a/app/src/androidTest/kotlin/com/tangem/screens/TestTopBar.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.screens - -import androidx.compose.ui.test.SemanticsNodeInteractionsProvider -import com.tangem.core.ui.test.TestTags -import io.github.kakaocup.compose.node.element.ComposeScreen -import io.github.kakaocup.compose.node.element.KNode - -class TestTopBar(semanticsProvider: SemanticsNodeInteractionsProvider) : - ComposeScreen( - semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(TestTags.MAIN_SCREEN_TOP_BAR) } - ) { - val moreButton: KNode = child { - hasTestTag(TestTags.MAIN_SCREEN_MORE_BUTTON) - } -} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/TopBarPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/TopBarPageObject.kt new file mode 100644 index 0000000000..e18c510849 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/TopBarPageObject.kt @@ -0,0 +1,21 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.MainScreenTestTags +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 + +class TopBarPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(MainScreenTestTags.TOP_BAR) } + ) { + val moreButton: KNode = child { + hasTestTag(MainScreenTestTags.MORE_BUTTON) + } +} + +internal fun BaseTestCase.onTopBar(function: TopBarPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/WalletSettingsTestScreen.kt b/app/src/androidTest/kotlin/com/tangem/screens/WalletSettingsPageObject.kt similarity index 62% rename from app/src/androidTest/kotlin/com/tangem/screens/WalletSettingsTestScreen.kt rename to app/src/androidTest/kotlin/com/tangem/screens/WalletSettingsPageObject.kt index 1b31bd509c..6a2a606ef5 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/WalletSettingsTestScreen.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/WalletSettingsPageObject.kt @@ -1,19 +1,21 @@ package com.tangem.screens import androidx.compose.ui.test.SemanticsNodeInteractionsProvider -import com.tangem.core.ui.test.TestTags +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.WalletSettingsScreenTestTags 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 -class WalletSettingsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : - ComposeScreen( +class WalletSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen( semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(TestTags.WALLET_SETTINGS_SCREEN) } + viewBuilderAction = { hasTestTag(WalletSettingsScreenTestTags.SCREEN_CONTAINER) } ) { private val walletSettingsItem: KNode = child { - hasTestTag(TestTags.WALLET_SETTINGS_SCREEN_ITEM) + hasTestTag(WalletSettingsScreenTestTags.SCREEN_ITEM) } val linkMoreCardsButton: KNode = walletSettingsItem.child { @@ -28,4 +30,7 @@ class WalletSettingsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvi val forgetWalletButton: KNode = walletSettingsItem.child { hasText(getResourceString(R.string.settings_forget_wallet)) } -} \ No newline at end of file +} + +internal fun BaseTestCase.onWalletSettingsScreen(function: WalletSettingsPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/DetailsScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/DetailsTest.kt similarity index 54% rename from app/src/androidTest/kotlin/com/tangem/tests/DetailsScreenTest.kt rename to app/src/androidTest/kotlin/com/tangem/tests/DetailsTest.kt index dd86a046cc..1242a1285e 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/DetailsScreenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/DetailsTest.kt @@ -4,152 +4,151 @@ import com.tangem.common.BaseTestCase import com.tangem.common.extensions.clickWithAssertion import com.tangem.domain.models.scan.ProductType import com.tangem.scenarios.OpenMainScreenScenario -import com.tangem.screens.DetailsTestScreen -import com.tangem.screens.TestTopBar -import com.tangem.screens.WalletSettingsTestScreen +import com.tangem.screens.onDetailsScreen +import com.tangem.screens.onTopBar +import com.tangem.screens.onWalletSettingsScreen import dagger.hilt.android.testing.HiltAndroidTest -import io.github.kakaocup.compose.node.element.ComposeScreen import org.junit.Test @HiltAndroidTest -class DetailsScreenTest : BaseTestCase() { +class DetailsTest : BaseTestCase() { @Test - fun walletWithoutBackupDetails() = + fun walletWithoutBackupDetailsTest() = setupHooks().run { scenario(OpenMainScreenScenario(composeTestRule)) - ComposeScreen.onComposeScreen(composeTestRule) { + onTopBar { step("Open wallet details") { moreButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(composeTestRule) { - step("Assert wallet connect button is visible") { + onDetailsScreen { + step("Assert 'Wallet connect' button is visible") { walletConnectButton.assertIsDisplayed() } - step("Assert scan card button is visible") { + step("Assert 'Scan card' button is visible") { scanCardButton.assertIsDisplayed() } - step("Assert buy Tangem card button is visible") { + step("Assert 'Buy Tangem card' button is visible") { buyTangemButton.assertIsDisplayed() } - step("Assert app settings button is visible") { + step("Assert 'App settings' button is visible") { appSettingsButton.assertIsDisplayed() } - step("Assert contact support button is visible") { + step("Assert 'Contact support' button is visible") { contactSupportButton.assertIsDisplayed() } - step("Assert terms or service button is visible") { + step("Assert 'Terms of service' button is visible") { toSButton.assertIsDisplayed() } - step("Open wallet settings screen") { + step("Open 'Wallet settings' screen") { walletNameButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(composeTestRule) { - step("Assert Link more cards button is visible") { + onWalletSettingsScreen { + step("Assert 'Link more cards' button is visible") { linkMoreCardsButton.assertIsDisplayed() } - step("Assert Card Settings button is visible") { + step("Assert 'Card Settings' button is visible") { cardSettingsButton.assertIsDisplayed() } - step("Assert Referral program button is visible") { + step("Assert 'Referral program' button is visible") { referralProgramButton.assertIsDisplayed() } - step("Assert Forget wallet button is visible") { + step("Assert 'Forget wallet' button is visible") { forgetWalletButton.assertIsDisplayed() } } } @Test - fun wallet2Details() = + fun wallet2DetailsTest() = setupHooks().run { scenario(OpenMainScreenScenario(composeTestRule, ProductType.Wallet2)) - ComposeScreen.onComposeScreen(composeTestRule) { + onTopBar { step("Open wallet details") { moreButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(composeTestRule) { - step("Assert wallet connect button is visible") { + onDetailsScreen { + step("Assert 'Wallet connect' button is visible") { walletConnectButton.assertIsDisplayed() } - step("Assert scan card button is visible") { + step("Assert 'Scan card' button is visible") { scanCardButton.assertIsDisplayed() } - step("Assert buy Tangem card button is visible") { + step("Assert 'Buy Tangem card' button is visible") { buyTangemButton.assertIsDisplayed() } - step("Assert app settings button is visible") { + step("Assert 'App settings' button is visible") { appSettingsButton.assertIsDisplayed() } - step("Assert contact support button is visible") { + step("Assert 'Contact support' button is visible") { contactSupportButton.assertIsDisplayed() } - step("Assert terms or service button is visible") { + step("Assert 'Terms or service' button is visible") { toSButton.assertIsDisplayed() } - step("Open wallet settings screen") { + step("Open 'Wallet settings' screen") { walletNameButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(composeTestRule) { - step("Assert Link more cards button does not exist") { + onWalletSettingsScreen { + step("Assert 'Link more cards' button does not exist") { linkMoreCardsButton.assertIsNotDisplayed() } - step("Assert Card Settings button is visible") { + step("Assert 'Card Settings' button is visible") { cardSettingsButton.assertIsDisplayed() } - step("Assert Referral program button is visible") { + step("Assert 'Referral program' button is visible") { referralProgramButton.assertIsDisplayed() } - step("Assert Forget wallet button is visible") { + step("Assert 'Forget wallet' button is visible") { forgetWalletButton.assertIsDisplayed() } } } @Test - fun noteDetails() = + fun noteDetailsTest() = setupHooks().run { scenario(OpenMainScreenScenario(composeTestRule, ProductType.Note)) - ComposeScreen.onComposeScreen(composeTestRule) { + onTopBar { step("Open wallet details") { moreButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(composeTestRule) { - step("Assert wallet connect button does not exist") { + onDetailsScreen { + step("Assert 'Wallet connect' button does not exist") { walletConnectButton.assertIsNotDisplayed() } - step("Assert scan card button is visible") { + step("Assert 'Scan card' button is visible") { scanCardButton.assertIsDisplayed() } - step("Assert buy Tangem card button is visible") { + step("Assert 'Buy Tangem card' button is visible") { buyTangemButton.assertIsDisplayed() } - step("Assert app settings button is visible") { + step("Assert 'App settings' button is visible") { appSettingsButton.assertIsDisplayed() } - step("Assert contact support button is visible") { + step("Assert 'Contact support' button is visible") { contactSupportButton.assertIsDisplayed() } - step("Assert terms or service button is visible") { + step("Assert 'Terms or service' button is visible") { toSButton.assertIsDisplayed() } - step("Open wallet settings screen") { + step("Open 'Wallet settings' screen") { walletNameButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(composeTestRule) { - step("Assert Card Settings button is visible") { + onWalletSettingsScreen { + step("Assert 'Card Settings' button is visible") { cardSettingsButton.assertIsDisplayed() } - step("Assert Referral program button does not exist") { + step("Assert 'Referral program' button does not exist") { referralProgramButton.assertIsNotDisplayed() } - step("Assert Forget wallet button is visible") { + step("Assert 'Forget wallet' button is visible") { forgetWalletButton.assertIsDisplayed() } } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/MainScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/MainScreenTest.kt index fd3a2df953..c708b29087 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/MainScreenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/MainScreenTest.kt @@ -1,11 +1,8 @@ package com.tangem.tests -import androidx.compose.ui.test.junit4.createAndroidComposeRule import com.tangem.common.BaseTestCase import com.tangem.scenarios.OpenMainScreenScenario -import com.tangem.tap.MainActivity import dagger.hilt.android.testing.HiltAndroidTest -import org.junit.Rule import org.junit.Test @HiltAndroidTest diff --git a/app/src/androidTest/kotlin/com/tangem/tests/ScanErrorTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/ScanErrorTest.kt index da686950a7..c31340f9dd 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/ScanErrorTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/ScanErrorTest.kt @@ -2,36 +2,35 @@ package com.tangem.tests import com.tangem.common.BaseTestCase import com.tangem.common.extensions.clickWithAssertion -import com.tangem.screens.DisclaimerTestScreen -import com.tangem.screens.MainTestScreen -import com.tangem.screens.StoriesTestScreen +import com.tangem.screens.onDisclaimerScreen +import com.tangem.screens.onMainScreen +import com.tangem.screens.onStoriesScreen import com.tangem.tap.domain.sdk.mocks.MockProvider import dagger.hilt.android.testing.HiltAndroidTest -import io.github.kakaocup.compose.node.element.ComposeScreen import org.junit.Test @HiltAndroidTest class ScanErrorTest : BaseTestCase() { @Test - fun goToMain() = + fun goToMainTest() = setupHooks().run { - ComposeScreen.onComposeScreen(composeTestRule) { - step("Click on \"Accept\" button") { + onDisclaimerScreen { + step("Click on 'Accept' button") { acceptButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(composeTestRule) { - step("Click on \"Scan\" button emulating scan error") { + onStoriesScreen { + step("Click on 'Scan' button emulating scan error") { MockProvider.setEmulateError() scanButton.clickWithAssertion() } - step("Click on \"Scan\" button again without emulating error") { + step("Click on 'Scan' button again without emulating error") { MockProvider.resetEmulateError() scanButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(composeTestRule) { + onMainScreen { step("Make sure wallet screen is visible") { assertIsDisplayed() } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt index eab4389f6b..c17f7fc08d 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt @@ -1,31 +1,28 @@ package com.tangem.tests import android.content.Intent.ACTION_VIEW -import androidx.test.espresso.intent.matcher.UriMatchers import com.tangem.common.BaseTestCase import com.tangem.common.extensions.clickWithAssertion -import com.tangem.screens.DisclaimerTestScreen -import com.tangem.screens.StoriesTestScreen +import com.tangem.screens.onDisclaimerScreen +import com.tangem.screens.onStoriesScreen import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL import dagger.hilt.android.testing.HiltAndroidTest -import io.github.kakaocup.compose.node.element.ComposeScreen import io.github.kakaocup.kakao.intent.KIntent -import org.hamcrest.Matchers import org.junit.Test @HiltAndroidTest class StoriesTest : BaseTestCase() { @Test - fun clickOnOrderButton() = + fun clickOnOrderButtonTest() = setupHooks().run { - ComposeScreen.onComposeScreen(composeTestRule) { - step("Click on \"Accept\" button") { + onDisclaimerScreen { + step("Click on 'Accept' button") { acceptButton.clickWithAssertion() } } - ComposeScreen.onComposeScreen(composeTestRule) { - step("Click on \"Order\" button") { + onStoriesScreen { + step("Click on 'Order' button") { orderButton.clickWithAssertion() } step("Assert: browser opened") { diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index aaeed5140b..ed9d07c577 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -39,8 +39,6 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles -import com.tangem.features.onramp.OnrampFeatureToggles -import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler import com.tangem.tap.common.log.TangemAppLoggerInitializer import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles @@ -118,8 +116,6 @@ interface ApplicationEntryPoint { fun getTransactionSignerFactory(): TransactionSignerFactory - fun getOnrampFeatureToggles(): OnrampFeatureToggles - fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles fun getOnboardingRepository(): OnboardingRepository @@ -141,8 +137,6 @@ interface ApplicationEntryPoint { fun getWorkerFactory(): HiltWorkerFactory - fun getOnlineCardVerifier(): OnlineCardVerifier - fun getColdUserWalletBuilderFactory(): ColdUserWalletBuilder.Factory fun getApiConfigsManager(): ApiConfigsManager diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index f073431daa..57b465765c 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -26,7 +26,6 @@ import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import arrow.core.getOrElse import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.RoutingFeatureToggle import com.tangem.common.routing.entity.SerializableIntent import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.context.AppComponentContext @@ -34,9 +33,7 @@ import com.tangem.core.decompose.di.RootAppComponentContext import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.WEBLINK_KEY import com.tangem.core.deeplink.converter.PayloadToDeeplinkConverter -import com.tangem.core.navigation.email.EmailSender import com.tangem.core.navigation.url.UrlOpener -import com.tangem.core.ui.UiDependencies import com.tangem.data.balancehiding.DefaultDeviceFlipDetector import com.tangem.data.card.sdk.CardSdkOwner import com.tangem.domain.apptheme.model.AppThemeMode @@ -53,6 +50,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION +import com.tangem.features.tester.api.TesterMenuLauncher import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.google.GoogleServicesHelper import com.tangem.operations.backup.BackupService @@ -69,7 +67,6 @@ import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandle import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler import com.tangem.tap.features.main.MainViewModel -import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.redux.DaggerGraphAction import com.tangem.tap.routing.component.RoutingComponent import com.tangem.tap.routing.configurator.AppRouterConfig @@ -103,9 +100,6 @@ val mainScope = CoroutineScope(mainCoroutineContext) @AndroidEntryPoint class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { - @Inject - lateinit var appStateHolder: AppStateHolder - /** Router for opening tester menu */ @Inject lateinit var cardSdkOwner: CardSdkOwner @@ -143,9 +137,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject lateinit var userWalletsListManager: UserWalletsListManager - @Inject - lateinit var emailSender: EmailSender - @Inject @RootAppComponentContext internal lateinit var rootComponentContext: AppComponentContext @@ -174,15 +165,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject lateinit var dispatchers: CoroutineDispatcherProvider - @Inject - internal lateinit var uiDependencies: UiDependencies - @Inject internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector - @Inject - internal lateinit var routingFeatureToggle: RoutingFeatureToggle - @Inject internal lateinit var deeplinkFactory: DeepLinkFactory @@ -192,6 +177,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject internal lateinit var urlOpener: UrlOpener + @Inject + internal lateinit var testerMenuLauncher: TesterMenuLauncher + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -244,13 +232,12 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { sendStakingUnsubmittedHashes() checkGoogleServicesAvailability() - if (routingFeatureToggle.isDeepLinkNavigationEnabled.not() && intent != null && savedInstanceState == null) { - // handle intent only on start, not on recreate - handleDeepLink(intent = intent, isFromOnNewIntent = false) - } - lifecycle.addObserver(WindowObscurationObserver) lifecycle.addObserver(defaultDeviceFlipDetector) + + if (BuildConfig.TESTER_MENU_ENABLED) { + lifecycle.addObserver(testerMenuLauncher.launchOnShakeObserver) + } } private fun setRootContent() { @@ -481,7 +468,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } } - if (routingFeatureToggle.isDeepLinkNavigationEnabled && intent != null) { + if (intent != null) { handleDeepLink(intent = intent, isFromOnNewIntent = false) } @@ -489,26 +476,22 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } private fun handleDeepLink(intent: Intent, isFromOnNewIntent: Boolean) { - if (routingFeatureToggle.isDeepLinkNavigationEnabled) { - val deepLinkExtras = PayloadToDeeplinkConverter.convertBundle(intent.extras)?.toUri() - val webLink = intent.getStringExtra(WEBLINK_KEY) + val deepLinkExtras = PayloadToDeeplinkConverter.convertBundle(intent.extras)?.toUri() + val webLink = intent.getStringExtra(WEBLINK_KEY) - val receivedDeepLink = intent.data ?: deepLinkExtras + val receivedDeepLink = intent.data ?: deepLinkExtras - when { - receivedDeepLink != null -> { - deeplinkFactory.handleDeeplink( - deeplinkUri = receivedDeepLink, - coroutineScope = lifecycleScope, - isFromOnNewIntent = isFromOnNewIntent, - ) - } - webLink?.uriValidate() == true -> { - urlOpener.openUrl(webLink) - } + when { + receivedDeepLink != null -> { + deeplinkFactory.handleDeeplink( + deeplinkUri = receivedDeepLink, + coroutineScope = lifecycleScope, + isFromOnNewIntent = isFromOnNewIntent, + ) + } + webLink?.uriValidate() == true -> { + urlOpener.openUrl(webLink) } - } else { - deepLinksRegistry.launch(intent) } } diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 621f3eed14..ef784f8347 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -58,8 +58,6 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles -import com.tangem.features.onramp.OnrampFeatureToggles -import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.operations.attestation.api.TangemApiServiceSettings import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder @@ -79,7 +77,6 @@ import com.tangem.wallet.BuildConfig import dagger.hilt.EntryPoints import kotlinx.coroutines.* import org.rekotlin.Store -import kotlin.collections.set import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository lateinit var store: Store @@ -186,9 +183,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat private val transactionSignerFactory: TransactionSignerFactory get() = entryPoint.getTransactionSignerFactory() - private val onrampFeatureToggles: OnrampFeatureToggles - get() = entryPoint.getOnrampFeatureToggles() - private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles get() = entryPoint.getOnboardingV2FeatureToggles() @@ -224,9 +218,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat .setWorkerFactory(workerFactory) .build() - private val onlineCardVerifier: OnlineCardVerifier - get() = entryPoint.getOnlineCardVerifier() - private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory get() = entryPoint.getColdUserWalletBuilderFactory() @@ -363,7 +354,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat shareManager = shareManager, appRouter = appRouter, transactionSignerFactory = transactionSignerFactory, - onrampFeatureToggles = onrampFeatureToggles, environmentConfigStorage = environmentConfigStorage, onboardingV2FeatureToggles = onboardingV2FeatureToggles, onboardingRepository = onboardingRepository, @@ -372,7 +362,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat clipboardManager = clipboardManager, settingsManager = settingsManager, uiMessageSender = uiMessageSender, - onlineCardVerifier = onlineCardVerifier, coldUserWalletBuilderFactory = coldUserWalletBuilderFactory, userTokensResponseStore = userTokensResponseStore, ), 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 acf3104c3f..55ebaa1ea3 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 @@ -3,16 +3,11 @@ package com.tangem.tap.common.extensions import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchainsdk.utils.amountToCreateAccount import com.tangem.common.services.Result -import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.tap.common.TestActions -import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import com.tangem.tap.domain.TapError import com.tangem.tap.domain.getFirstToken -import com.tangem.tap.domain.model.WalletAddressData -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import kotlinx.coroutines.delay @@ -57,43 +52,4 @@ suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result = try } } } -} - -internal fun WalletManager.getTopUpUrl(cryptoCurrency: CryptoCurrency): String? { - val globalState = store.state.globalState - val defaultAddress = wallet.address - - return globalState.exchangeManager.getUrl( - action = CurrencyExchangeManager.Action.Buy, - cryptoCurrency = cryptoCurrency, - fiatCurrencyName = globalState.appCurrency.code, - walletAddress = defaultAddress, - isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive, - ) -} - -internal fun WalletManager?.getAddressData(): WalletAddressData? { - val wallet = this?.wallet ?: return null - - val addressDataList = wallet.createAddressesData() - return if (addressDataList.isEmpty()) null else addressDataList[0] -} - -private fun Wallet.createAddressesData(): List { - val listOfAddressData = mutableListOf() - // put a defaultAddress at the first place - addresses.forEach { - val addressData = WalletAddressData( - it.value, - it.type, - getShareUri(it.value), - getExploreUrl(it.value), - ) - if (it.type == AddressType.Default) { - listOfAddressData.add(0, addressData) - } else { - listOfAddressData.add(addressData) - } - } - return listOfAddressData } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/images/Coil.kt b/app/src/main/java/com/tangem/tap/common/images/Coil.kt index 28336b233f..328a1d431e 100644 --- a/app/src/main/java/com/tangem/tap/common/images/Coil.kt +++ b/app/src/main/java/com/tangem/tap/common/images/Coil.kt @@ -6,6 +6,7 @@ import android.util.Log import coil.ImageLoader import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder +import coil.decode.SvgDecoder import coil.memory.MemoryCache import coil.request.CachePolicy import coil.util.Logger @@ -36,6 +37,7 @@ fun createCoilImageLoader(context: Context, logEnabled: Boolean = false): ImageL } else { add(GifDecoder.Factory()) } + add(SvgDecoder.Factory()) } .memoryCachePolicy(CachePolicy.ENABLED) .memoryCache { diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 0a43a25887..404c7150f4 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -30,21 +30,5 @@ sealed class GlobalAction : Action { data class Success(val appCurrency: AppCurrency) : GlobalAction() } - data class UpdateWalletSignedHashes( - val walletSignedHashes: Int?, - val remainingSignatures: Int?, - val walletPublicKey: ByteArray, - ) : GlobalAction() - data class IsSignWithRing(val isSignWithRing: Boolean) : GlobalAction() - - object ExchangeManager : GlobalAction() { - object Init : GlobalAction() { - data class Success( - val exchangeManager: com.tangem.tap.network.exchangeServices.CurrencyExchangeManager, - ) : GlobalAction() - } - - object Update : GlobalAction() - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index a75849544c..7e4af406fb 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -2,23 +2,14 @@ package com.tangem.tap.common.redux.global import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.guard import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.datasource.local.config.environment.EnvironmentConfig import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.common.LogConfig -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.StateDialog -import com.tangem.domain.wallets.models.requireColdWallet -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.dispatchDialogShow +import com.tangem.tap.common.extensions.dispatchWithMain +import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.redux.AppState -import com.tangem.tap.network.exchangeServices.CardExchangeRules -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager -import com.tangem.tap.network.exchangeServices.ExchangeService -import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoEnvironment -import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService -import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store @@ -31,17 +22,17 @@ object GlobalMiddleware { val handler = globalMiddlewareHandler } -private val globalMiddlewareHandler: Middleware = { dispatch, appState -> +private val globalMiddlewareHandler: Middleware = { _, _ -> { nextDispatch -> { action -> - handleAction(action, appState) + handleAction(action) nextDispatch(action) } } } @Suppress("LongMethod", "ComplexMethod") -private fun handleAction(action: Action, appState: () -> AppState?) { +private fun handleAction(action: Action) { when (action) { is GlobalAction.ScanFailsCounter.ChooseBehavior -> { when (action.result) { @@ -51,42 +42,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) { } } } - is GlobalAction.RestoreAppCurrency -> { - restoreAppCurrency() - } - is GlobalAction.ExchangeManager.Init -> { - val config = store.inject(DaggerGraphState::environmentConfigStorage).getConfigSync() - - scope.launch { - val scanResponseProvider: () -> ScanResponse? = { - val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - userWalletsListManager.selectedUserWalletSync?.requireColdWallet()?.scanResponse // TODO [REDACTED_TASK_KEY] - } - val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card } - - val buyService = makeBuyExchangeService(config) - val sellService = makeSellExchangeService(config) - val exchangeManager = CurrencyExchangeManager( - buyService = buyService, - sellService = sellService, - primaryRules = CardExchangeRules(cardProvider), - ) - // TODO: for refactoring (after remove old design refactor CurrencyExchangeManager and use 1 instance) - store.inject(DaggerGraphState::appStateHolder).buyService = buyService - store.inject(DaggerGraphState::appStateHolder).sellService = sellService - store.inject(DaggerGraphState::appStateHolder).exchangeService = exchangeManager - store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager)) - store.dispatchOnMain(GlobalAction.ExchangeManager.Update) - } - } - is GlobalAction.ExchangeManager.Init.Success -> {} - is GlobalAction.ExchangeManager.Update -> { - val exchangeManager = appState()?.globalState?.exchangeManager.guard { - store.dispatchDebugErrorNotification("exchangeManager is not initialized") - return - } - scope.launch { exchangeManager.update() } - } + is GlobalAction.RestoreAppCurrency -> restoreAppCurrency() } } @@ -119,21 +75,4 @@ private fun restoreAppCurrency() { store.dispatchWithMain(GlobalAction.RestoreAppCurrency.Success(currency)) } -} - -private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): ExchangeService { - return MoonPayService( - apiKey = environmentConfig.moonPayApiKey, - secretKey = environmentConfig.moonPayApiSecretKey, - logEnabled = LogConfig.network.moonPayService, - ) -} - -private fun makeBuyExchangeService(environmentConfig: EnvironmentConfig): ExchangeService { - return MercuryoService( - environment = MercuryoEnvironment.prod( - widgetId = environmentConfig.mercuryoWidgetId, - secret = environmentConfig.mercuryoSecret, - ), - ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 51da092058..a1ed71be61 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -1,7 +1,6 @@ package com.tangem.tap.common.redux.global import com.tangem.tap.common.redux.AppState -import com.tangem.utils.extensions.replaceBy import org.rekotlin.Action @Suppress("LongMethod", "ComplexMethod") @@ -26,24 +25,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.RestoreAppCurrency.Success -> { globalState.copy(appCurrency = action.appCurrency) } - is GlobalAction.UpdateWalletSignedHashes -> { - val card = globalState.scanResponse?.card ?: return globalState - val wallet = card.wallets - .firstOrNull { it.publicKey.contentEquals(action.walletPublicKey) } - ?: return globalState - - val newCardInstance = card.copy( - wallets = card.wallets.toMutableList().also { walletsMutable -> - walletsMutable.replaceBy( - item = wallet.copy( - totalSignedHashes = action.walletSignedHashes, - remainingSignatures = action.remainingSignatures, - ), - ) { it.index == wallet.index } - }, - ) - globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance)) - } is GlobalAction.IsSignWithRing -> globalState.copy(isLastSignWithRing = action.isSignWithRing) is GlobalAction.ShowDialog -> { globalState.copy(dialog = action.stateDialog) @@ -51,9 +32,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.HideDialog -> { globalState.copy(dialog = null) } - is GlobalAction.ExchangeManager.Init.Success -> { - globalState.copy(exchangeManager = action.exchangeManager) - } else -> globalState } } \ No newline at end of file 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 fb6bf4ae13..090cbc72f8 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 @@ -4,8 +4,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.StateDialog import com.tangem.tap.domain.TapWalletManager -import com.tangem.tap.features.onboarding.OnboardingManager -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import org.rekotlin.StateType data class GlobalState( @@ -16,7 +14,6 @@ data class GlobalState( val appCurrency: AppCurrency = AppCurrency.Default, val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, - val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(), val isLastSignWithRing: Boolean = false, ) : StateType @@ -24,6 +21,5 @@ typealias CryptoCurrencyName = String data class OnboardingState( val onboardingStarted: Boolean = false, - val onboardingManager: OnboardingManager? = null, val shouldResetOnCreate: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/data/DefaultCardSdkProvider.kt b/app/src/main/java/com/tangem/tap/data/DefaultCardSdkProvider.kt index 052cdbf1e2..d113493313 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultCardSdkProvider.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultCardSdkProvider.kt @@ -16,16 +16,13 @@ import com.tangem.crypto.bip39.Wordlist import com.tangem.data.card.sdk.CardSdkOwner import com.tangem.data.card.sdk.CardSdkProvider import com.tangem.datasource.api.common.config.ApiConfig -import com.tangem.datasource.api.common.config.ApiEnvironment +import com.tangem.datasource.api.common.config.ApiEnvironmentConfig import com.tangem.datasource.api.common.config.managers.ApiConfigsManager -import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.PreferencesKeys -import com.tangem.datasource.local.preferences.utils.getObjectMap +import com.tangem.datasource.api.common.config.managers.MutableApiConfigsManager import com.tangem.datasource.utils.AddHeadersInterceptor import com.tangem.datasource.utils.RequestHeader import com.tangem.operations.attestation.api.TangemApiServiceSettings import com.tangem.sdk.DefaultSessionViewDelegate -import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles import com.tangem.sdk.extensions.* import com.tangem.sdk.nfc.AndroidNfcAvailabilityProvider import com.tangem.sdk.nfc.NfcManager @@ -34,13 +31,6 @@ import com.tangem.tap.foregroundActivityObserver import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.info.AppInfoProvider import com.tangem.utils.version.AppVersionProvider -import com.tangem.wallet.BuildConfig -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking import javax.inject.Inject import javax.inject.Singleton @@ -55,11 +45,9 @@ import javax.inject.Singleton internal class DefaultCardSdkProvider @Inject constructor( private val analyticsExceptionHandler: AnalyticsExceptionHandler, private val dispatchers: CoroutineDispatcherProvider, - private val cardSdkFeatureToggles: CardSdkFeatureToggles, private val apiConfigsManager: ApiConfigsManager, appVersionProvider: AppVersionProvider, appInfoProvider: AppInfoProvider, - appPreferencesStore: AppPreferencesStore, ) : CardSdkProvider, CardSdkOwner { private val observer = Observer() @@ -70,26 +58,15 @@ internal class DefaultCardSdkProvider @Inject constructor( get() = holder?.sdk ?: tryToRegisterWithForegroundActivity() init { - if (BuildConfig.TESTER_MENU_ENABLED) { - appPreferencesStore.getObjectMap(PreferencesKeys.apiConfigsEnvironmentKey) - .map { - when (it[ApiConfig.ID.Attestation.name]) { - ApiEnvironment.DEV, - ApiEnvironment.STAGE, - -> false - ApiEnvironment.PROD, - null, - -> true - } + val mutableManager = apiConfigsManager as? MutableApiConfigsManager + + mutableManager?.addListener( + object : MutableApiConfigsManager.ApiConfigEnvChangeListener(id = ApiConfig.ID.TangemTech) { + override fun onChange(environmentConfig: ApiEnvironmentConfig) { + holder?.sdk?.config?.tangemApiBaseUrl = environmentConfig.baseUrl } - .distinctUntilChanged() - .onEach { isProd -> - holder?.let { - it.sdk.config.isTangemAttestationProdEnv = isProd - } - } - .launchIn(CoroutineScope(SupervisorJob() + dispatchers.main)) - } + }, + ) TangemApiServiceSettings.addInterceptors( AddHeadersInterceptor( @@ -187,10 +164,8 @@ internal class DefaultCardSdkProvider @Inject constructor( keystoreManager = keystoreManager, wordlist = Wordlist.getWordlist(activity), config = config.apply { - isNewOnlineAttestationEnabled = cardSdkFeatureToggles.isNewAttestationEnabled - - val apiConfig = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.Attestation) - isTangemAttestationProdEnv = apiConfig.environment == ApiEnvironment.PROD + val apiConfig = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.TangemTech) + tangemApiBaseUrl = apiConfig.baseUrl }, ) diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index c527434b1c..e963c1f140 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -9,7 +9,6 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository -import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.network.exchangeServices.DefaultRampManager @@ -51,17 +50,13 @@ internal object ActivityModule { currenciesRepository: CurrenciesRepository, excludedBlockchains: ExcludedBlockchains, dispatchers: CoroutineDispatcherProvider, - onrampFeatureToggles: OnrampFeatureToggles, ): RampStateManager { return DefaultRampManager( - exchangeService = appStateHolder.exchangeService, - buyService = Provider { requireNotNull(appStateHolder.buyService) }, sellService = Provider { requireNotNull(appStateHolder.sellService) }, expressServiceLoader = expressServiceLoader, currenciesRepository = currenciesRepository, - excludedBlockchains = excludedBlockchains, dispatchers = dispatchers, - onrampFeatureToggles = onrampFeatureToggles, + excludedBlockchains = excludedBlockchains, ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt index 38eae7aec7..43391888d5 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt @@ -8,6 +8,7 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletmanager.WalletManagersFacade @@ -75,6 +76,7 @@ internal object ManageTokensDomainModule { multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, tokensFeatureToggles: TokensFeatureToggles, ): SaveManagedTokensUseCase { return SaveManagedTokensUseCase( @@ -86,6 +88,7 @@ internal object ManageTokensDomainModule { multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles = tokensFeatureToggles, ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt index 5f525bc14c..dcedc27851 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt @@ -5,6 +5,8 @@ import com.tangem.domain.nft.* import com.tangem.domain.nft.repository.NFTRepository import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier +import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -33,9 +35,13 @@ internal object NFTDomainModule { fun providesFetchNFTCollectionsUseCase( currenciesRepository: CurrenciesRepository, nftRepository: NFTRepository, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): FetchNFTCollectionsUseCase = FetchNFTCollectionsUseCase( currenciesRepository = currenciesRepository, nftRepository = nftRepository, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) @Provides @@ -43,9 +49,13 @@ internal object NFTDomainModule { fun providesRefreshAllNFTUseCase( currenciesRepository: CurrenciesRepository, nftRepository: NFTRepository, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): RefreshAllNFTUseCase = RefreshAllNFTUseCase( currenciesRepository = currenciesRepository, nftRepository = nftRepository, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) @Provides @@ -118,8 +128,16 @@ internal object NFTDomainModule { walletsRepository: WalletsRepository, nftRepository: NFTRepository, currenciesRepository: CurrenciesRepository, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): DisableWalletNFTUseCase { - return DisableWalletNFTUseCase(walletsRepository, nftRepository, currenciesRepository) + return DisableWalletNFTUseCase( + walletsRepository = walletsRepository, + nftRepository = nftRepository, + currenciesRepository = currenciesRepository, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, + ) } @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 c75aefa33c..5806f4ecae 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 @@ -22,6 +22,7 @@ import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations 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.wallet.WalletBalanceFetcher import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -44,6 +45,7 @@ internal object TokensDomainModule { multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, tokensFeatureToggles: TokensFeatureToggles, ): AddCryptoCurrenciesUseCase { return AddCryptoCurrenciesUseCase( @@ -52,6 +54,7 @@ internal object TokensDomainModule { multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles = tokensFeatureToggles, ) } @@ -107,8 +110,15 @@ internal object TokensDomainModule { fun provideRemoveCurrencyUseCase( currenciesRepository: CurrenciesRepository, walletManagersFacade: WalletManagersFacade, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): RemoveCurrencyUseCase { - return RemoveCurrencyUseCase(currenciesRepository, walletManagersFacade) + return RemoveCurrencyUseCase( + currenciesRepository = currenciesRepository, + walletManagersFacade = walletManagersFacade, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, + ) } @Provides @@ -145,6 +155,8 @@ internal object TokensDomainModule { currencyChecksRepository: CurrencyChecksRepository, dispatchers: CoroutineDispatcherProvider, baseCurrencyStatusOperations: BaseCurrencyStatusOperations, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): GetCurrencyWarningsUseCase { return GetCurrencyWarningsUseCase( walletManagersFacade = walletManagersFacade, @@ -152,6 +164,8 @@ internal object TokensDomainModule { dispatchers = dispatchers, currencyChecksRepository = currencyChecksRepository, currencyStatusOperations = baseCurrencyStatusOperations, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) } @@ -163,6 +177,7 @@ internal object TokensDomainModule { singleNetworkStatusFetcher: SingleNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, tokensFeatureToggles: TokensFeatureToggles, ): FetchCurrencyStatusUseCase { return FetchCurrencyStatusUseCase( @@ -171,6 +186,7 @@ internal object TokensDomainModule { singleNetworkStatusFetcher = singleNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles = tokensFeatureToggles, ) } @@ -197,8 +213,12 @@ internal object TokensDomainModule { @Provides @Singleton - fun provideGetCryptoCurrencyUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrencyUseCase { - return GetCryptoCurrencyUseCase(currenciesRepository) + fun provideGetCryptoCurrencyUseCase( + currenciesRepository: CurrenciesRepository, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, + ): GetCryptoCurrencyUseCase { + return GetCryptoCurrencyUseCase(currenciesRepository, multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles) } @Provides @@ -219,9 +239,16 @@ internal object TokensDomainModule { @Singleton fun provideApplyTokenListSortingUseCase( currenciesRepository: CurrenciesRepository, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, dispatchers: CoroutineDispatcherProvider, ): ApplyTokenListSortingUseCase { - return ApplyTokenListSortingUseCase(currenciesRepository, dispatchers) + return ApplyTokenListSortingUseCase( + currenciesRepository = currenciesRepository, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, + dispatchers = dispatchers, + ) } @Provides @@ -280,9 +307,13 @@ internal object TokensDomainModule { @Singleton fun provideIsCryptoCurrencyCoinCouldHideUseCase( currenciesRepository: CurrenciesRepository, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): IsCryptoCurrencyCoinCouldHideUseCase { return IsCryptoCurrencyCoinCouldHideUseCase( currenciesRepository = currenciesRepository, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) } @@ -300,9 +331,16 @@ internal object TokensDomainModule { @Singleton fun provideGetBalanceNotEnoughForFeeWarningUseCase( currenciesRepository: CurrenciesRepository, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, dispatchers: CoroutineDispatcherProvider, ): GetBalanceNotEnoughForFeeWarningUseCase { - return GetBalanceNotEnoughForFeeWarningUseCase(currenciesRepository, dispatchers) + return GetBalanceNotEnoughForFeeWarningUseCase( + currenciesRepository = currenciesRepository, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, + dispatchers = dispatchers, + ) } @Provides @@ -343,10 +381,14 @@ internal object TokensDomainModule { fun provideRefreshMultiCurrencyWalletQuotesUseCase( currenciesRepository: CurrenciesRepository, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): RefreshMultiCurrencyWalletQuotesUseCase { return RefreshMultiCurrencyWalletQuotesUseCase( currenciesRepository = currenciesRepository, multiQuoteStatusFetcher = multiQuoteStatusFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) } @@ -374,6 +416,7 @@ internal object TokensDomainModule { singleQuoteStatusSupplier: SingleQuoteStatusSupplier, singleYieldBalanceSupplier: SingleYieldBalanceSupplier, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, ): BaseCurrenciesStatusesOperations { return CachedCurrenciesStatusesOperations( currenciesRepository = currenciesRepository, @@ -388,6 +431,7 @@ internal object TokensDomainModule { singleYieldBalanceSupplier = singleYieldBalanceSupplier, multiYieldBalanceFetcher = multiYieldBalanceFetcher, tokensFeatureToggles = tokensFeatureToggles, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, ) } @@ -406,6 +450,7 @@ internal object TokensDomainModule { singleQuoteStatusSupplier: SingleQuoteStatusSupplier, singleYieldBalanceSupplier: SingleYieldBalanceSupplier, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, ): BaseCurrencyStatusOperations { return CachedCurrenciesStatusesOperations( currenciesRepository = currenciesRepository, @@ -419,6 +464,7 @@ internal object TokensDomainModule { singleQuoteStatusSupplier = singleQuoteStatusSupplier, singleYieldBalanceSupplier = singleYieldBalanceSupplier, multiYieldBalanceFetcher = multiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles = tokensFeatureToggles, ) } @@ -428,4 +474,26 @@ internal object TokensDomainModule { fun provideGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase { return GetCryptoCurrenciesUseCase(currenciesRepository) } + + @Provides + @Singleton + fun provideWalletBalanceFetcher( + currenciesRepository: CurrenciesRepository, + multiWalletCryptoCurrenciesFetcher: MultiWalletCryptoCurrenciesFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + multiNetworkStatusFetcher: MultiNetworkStatusFetcher, + multiQuoteStatusFetcher: MultiQuoteStatusFetcher, + multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + dispatchers: CoroutineDispatcherProvider, + ): WalletBalanceFetcher { + return WalletBalanceFetcher( + currenciesRepository = currenciesRepository, + multiWalletCryptoCurrenciesFetcher = multiWalletCryptoCurrenciesFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + multiNetworkStatusFetcher = multiNetworkStatusFetcher, + multiQuoteStatusFetcher = multiQuoteStatusFetcher, + multiYieldBalanceFetcher = multiYieldBalanceFetcher, + dispatchers = dispatchers, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index 8be21bb8cf..0e1a88fec9 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -4,6 +4,8 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.demo.DemoConfig import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.networks.single.SingleNetworkStatusSupplier +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier +import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.TransactionRepository @@ -58,12 +60,16 @@ internal object TransactionDomainModule { walletManagersFacade: WalletManagersFacade, currenciesRepository: CurrenciesRepository, singleNetworkStatusSupplier: SingleNetworkStatusSupplier, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): AssociateAssetUseCase { return AssociateAssetUseCase( cardSdkConfigRepository = cardSdkConfigRepository, walletManagersFacade = walletManagersFacade, currenciesRepository = currenciesRepository, singleNetworkStatusSupplier = singleNetworkStatusSupplier, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) } 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 684a7f0cf7..26467ac00e 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 @@ -15,10 +15,7 @@ import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.* import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase -import com.tangem.operations.attestation.OnlineCardVerifier -import com.tangem.features.nft.NFTFeatureToggles import com.tangem.operations.attestation.CardArtworksProvider -import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -191,28 +188,14 @@ internal object WalletsDomainModule { @Provides @Singleton - fun provideGetCardImageUseCase( - onlineCardVerifier: OnlineCardVerifier, - cardArtworksProvider: CardArtworksProvider, - cardSdkFeatureToggles: CardSdkFeatureToggles, - ): GetCardImageUseCase { - return GetCardImageUseCase( - verifier = onlineCardVerifier, - cardArtworksProvider = cardArtworksProvider, - cardSdkFeatureToggles = cardSdkFeatureToggles, - ) + fun provideGetCardImageUseCase(cardArtworksProvider: CardArtworksProvider): GetCardImageUseCase { + return GetCardImageUseCase(cardArtworksProvider = cardArtworksProvider) } @Provides @Singleton - fun providesIsWalletNFTEnabledSyncUseCase( - walletsRepository: WalletsRepository, - nftFeatureToggles: NFTFeatureToggles, - ): IsWalletNFTEnabledSyncUseCase { - return IsWalletNFTEnabledSyncUseCase( - walletsRepository = walletsRepository, - nftFeatureToggles = nftFeatureToggles, - ) + fun providesIsWalletNFTEnabledSyncUseCase(walletsRepository: WalletsRepository): IsWalletNFTEnabledSyncUseCase { + return IsWalletNFTEnabledSyncUseCase(walletsRepository = walletsRepository) } @Provides diff --git a/app/src/main/java/com/tangem/tap/di/hot/TangemHotSdkModule.kt b/app/src/main/java/com/tangem/tap/di/hot/TangemHotSdkModule.kt new file mode 100644 index 0000000000..c21b55928b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/hot/TangemHotSdkModule.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.di.hot + +import com.tangem.hot.sdk.TangemHotSdk +import com.tangem.tap.features.hot.TangemHotSDKProxy +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface TangemHotSdkModule { + + @Binds + @Singleton + fun bindTangemHotSdk(proxy: TangemHotSDKProxy): TangemHotSdk +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt b/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt index a95acf5cd6..3bfc0b3a44 100644 --- a/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt +++ b/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt @@ -1,9 +1,7 @@ package com.tangem.tap.di.routing import com.tangem.common.routing.AppRouter -import com.tangem.common.routing.RoutingFeatureToggle import com.tangem.core.analytics.api.AnalyticsExceptionHandler -import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.tap.routing.ProxyAppRouter import com.tangem.tap.routing.configurator.AppRouterConfig import com.tangem.tap.routing.configurator.MutableAppRouterConfig @@ -33,10 +31,4 @@ internal object AppRouterModule { @Provides @Singleton fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig() - - @Provides - @Singleton - fun provideRoutingFeatureToggle(featureTogglesManager: FeatureTogglesManager): RoutingFeatureToggle { - return RoutingFeatureToggle(featureTogglesManager) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt deleted file mode 100644 index 547dfbd6ce..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.tangem.tap.domain.extensions - -import com.tangem.common.extensions.toHexString -import com.tangem.common.services.Result -import com.tangem.domain.common.TwinCardNumber -import com.tangem.domain.common.getTwinCardNumber -import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.wallets.models.Artwork -import com.tangem.operations.attestation.CardArtworksProvider -import com.tangem.operations.attestation.OnlineCardVerifier -import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo - -fun CardDTO.signedHashesCount(): Int { - return wallets.sumOf { it.totalSignedHashes ?: 0 } -} - -suspend fun CardDTO.getOrLoadCardArtworkUrl( - cardInfo: Result? = null, - onlineCardVerifier: OnlineCardVerifier, -): String { - fun ifAnyError(): String { - return when { - cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL - cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL - else -> { - when (getTwinCardNumber()) { - TwinCardNumber.First -> Artwork.TWIN_CARD_1_URL - TwinCardNumber.Second -> Artwork.TWIN_CARD_2_URL - else -> Artwork.DEFAULT_IMG_URL - } - } - } - } - - return when (val cardInfoResult = cardInfo ?: onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) { - is Result.Success -> { - val artworkId = cardInfoResult.data.artwork?.id - if (artworkId.isNullOrEmpty()) { - ifAnyError() - } else { - CardArtworksProvider.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId) - } - } - - is Result.Failure -> ifAnyError() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt index 2d1295d3b6..b733c77ec7 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt @@ -8,7 +8,7 @@ import com.reown.android.relay.ConnectionType import com.reown.walletkit.client.Wallet import com.reown.walletkit.client.WalletKit import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.data.walletconnect.pair.unsupportedDApps +import com.tangem.data.walletconnect.pair.UnsupportedDApps import com.tangem.domain.walletconnect.WcPairService import com.tangem.domain.walletconnect.model.WcPairRequest import com.tangem.domain.walletconnect.model.legacy.Account @@ -216,7 +216,7 @@ internal class DefaultLegacyWalletConnectRepository( Timber.i("sessionProposal: $sessionProposal") this@DefaultLegacyWalletConnectRepository.sessionProposal = sessionProposal - if (sessionProposal.name in unsupportedDApps) { + if (sessionProposal.name in UnsupportedDApps.list) { Timber.i("Unsupported DApp") scope.launch { _events.emit( diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index 577ac2f3a8..8427ef3c2f 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -43,9 +43,6 @@ object DemoHelper { } private fun getScanResponse(appState: () -> AppState?): ScanResponse? { - val state = appState() ?: return null - - return state.globalState.onboardingState.onboardingManager?.scanResponse - ?: state.globalState.scanResponse + return appState()?.globalState?.scanResponse } } \ No newline at end of file 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 689afdf90d..babdcb09be 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 @@ -27,7 +27,6 @@ import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.redux.AppDialog -import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.features.details.ui.cardsettings.CardInfo import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent @@ -171,6 +170,8 @@ internal class CardSettingsModel @Inject constructor( } } + private fun CardDTO.signedHashesCount(): Int = wallets.sumOf { it.totalSignedHashes ?: 0 } + private fun handleClickingItem(item: CardInfo) { when (item) { is CardInfo.ChangeAccessCode -> { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt index 762b7afb71..a231426ebb 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectModel.kt @@ -5,6 +5,7 @@ import arrow.core.getOrElse import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.qrscanning.models.QrResultSource import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction @@ -31,12 +32,18 @@ internal class WalletConnectModel @Inject constructor( init { modelScope.launch { - listenToQrScanningUseCase(SourceType.WALLET_CONNECT) + listenToQrScanningUseCase.listen(SourceType.WALLET_CONNECT) .getOrElse { emptyFlow() } - .map { + .map { result -> + val source = when (result.resultSource) { + QrResultSource.CLIPBOARD -> WalletConnectAction.OpenSession.SourceType.CLIPBOARD + QrResultSource.CAMERA, + QrResultSource.GALLERY, + -> WalletConnectAction.OpenSession.SourceType.QR + } WalletConnectAction.OpenSession( - wcUri = it, - source = WalletConnectAction.OpenSession.SourceType.QR, + wcUri = result.qrCode, + source = source, userWalletId = params.userWalletId, ) } diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index 88f9d53d3b..3ccd4ba7fe 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.test.TestTags +import com.tangem.core.ui.test.StoriesScreenTestTags import com.tangem.tap.features.home.compose.content.* import com.tangem.tap.features.home.compose.views.HomeButtons import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton @@ -61,7 +61,7 @@ internal fun StoriesScreen( StoriesScreenContent( modifier = Modifier .fillMaxSize() - .testTag(TestTags.STORIES_SCREEN), + .testTag(StoriesScreenTestTags.SCREEN_CONTAINER), config = StoriesScreenContentConfig( storiesSize = state.stories.lastIndex, currentStoryIndex = currentStoryIndex, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreenV2.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreenV2.kt index a17f707453..5ba5f44315 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreenV2.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreenV2.kt @@ -19,7 +19,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.test.TestTags +import com.tangem.core.ui.test.StoriesScreenTestTags import com.tangem.tap.features.home.compose.content.* import com.tangem.tap.features.home.compose.views.HomeButtonsV2 import com.tangem.tap.features.home.compose.views.StoriesProgressBar @@ -57,7 +57,7 @@ internal fun StoriesScreenV2( StoriesScreenContentV2( modifier = Modifier .fillMaxSize() - .testTag(TestTags.STORIES_SCREEN), + .testTag(StoriesScreenTestTags.SCREEN_CONTAINER), config = StoriesScreenContentV2Config( storiesSize = state.stories.lastIndex, currentStoryIndex = currentStoryIndex, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index 6b7987848b..eab6ba4ced 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -17,7 +17,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.test.TestTags +import com.tangem.core.ui.test.StoriesScreenTestTags import com.tangem.wallet.R @Composable @@ -34,7 +34,7 @@ internal fun HomeButtons( ScanCardButton( modifier = Modifier .weight(weight = 1f) - .testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON), + .testTag(StoriesScreenTestTags.SCAN_BUTTON), showProgress = btnScanStateInProgress, onClick = onScanButtonClick, ) @@ -42,7 +42,7 @@ internal fun HomeButtons( OrderCardButton( modifier = Modifier .weight(weight = 1f) - .testTag(TestTags.STORIES_SCREEN_ORDER_BUTTON), + .testTag(StoriesScreenTestTags.ORDER_BUTTON), onClick = onShopButtonClick, ) } diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtonsV2.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtonsV2.kt index 6b620da6dc..dc2192b603 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtonsV2.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtonsV2.kt @@ -18,7 +18,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.test.TestTags +import com.tangem.core.ui.test.StoriesScreenTestTags import com.tangem.wallet.R @Composable @@ -37,19 +37,19 @@ internal fun HomeButtonsV2( CreateNewWalletButton( modifier = Modifier .fillMaxWidth() - .testTag(TestTags.STORIES_SCREEN_CREATE_NEW_WALLET_BUTTON), + .testTag(StoriesScreenTestTags.CREATE_NEW_WALLET_BUTTON), onClick = onCreateNewWalletButtonClick, ) AddExistingWalletButton( modifier = Modifier .fillMaxWidth() - .testTag(TestTags.STORIES_SCREEN_ADD_EXISTING_WALLET_BUTTON), + .testTag(StoriesScreenTestTags.ADD_EXISTING_WALLET_BUTTON), onClick = onAddExistingWalletButtonClick, ) ScanCardButton( modifier = Modifier .fillMaxWidth() - .testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON), + .testTag(StoriesScreenTestTags.SCAN_BUTTON), showProgress = btnScanStateInProgress, onClick = onScanButtonClick, ) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index d020b24f18..c2882e49f7 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -61,7 +61,6 @@ private fun handleHomeAction(action: Action) { Analytics.send(IntroductionProcess.ScreenOpened()) store.dispatch(GlobalAction.RestoreAppCurrency) - store.dispatch(GlobalAction.ExchangeManager.Init) } is HomeAction.ReadCard -> { action.scope.launch { diff --git a/app/src/main/java/com/tangem/tap/features/hot/TangemHotSDKProxy.kt b/app/src/main/java/com/tangem/tap/features/hot/TangemHotSDKProxy.kt new file mode 100644 index 0000000000..9cc8db4191 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/hot/TangemHotSDKProxy.kt @@ -0,0 +1,53 @@ +package com.tangem.tap.features.hot + +import com.tangem.crypto.bip39.Mnemonic +import com.tangem.hot.sdk.TangemHotSdk +import com.tangem.hot.sdk.model.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withTimeout +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Proxy for [TangemHotSdk] to allow lazy initialization and provide a way to access the SDK state from domain layer. + * SDK is initialized in the [com.tangem.tap.routing.component.RoutingComponent] and can be accessed through this proxy. + * Be aware that the SDK is initialized on activity creation, so it may not be available immediately. + */ +@Singleton +class TangemHotSDKProxy @Inject constructor() : TangemHotSdk { + + val sdkState = MutableStateFlow(null) + + override suspend fun importWallet(mnemonic: Mnemonic, passphrase: CharArray?, auth: HotAuth): HotWalletId = + callSdk { importWallet(mnemonic, passphrase, auth) } + + override suspend fun generateWallet(auth: HotAuth, mnemonicType: MnemonicType): HotWalletId = + callSdk { generateWallet(auth, mnemonicType) } + + override suspend fun exportMnemonic(unlockHotWallet: UnlockHotWallet): SeedPhrasePrivateInfo = + callSdk { exportMnemonic(unlockHotWallet) } + + override suspend fun exportBackup(unlockHotWallet: UnlockHotWallet): ByteArray = + callSdk { exportBackup(unlockHotWallet) } + + override suspend fun delete(id: HotWalletId) = callSdk { delete(id) } + + override suspend fun changeAuth(unlockHotWallet: UnlockHotWallet, auth: HotAuth): HotWalletId = + callSdk { changeAuth(unlockHotWallet, auth) } + + override suspend fun derivePublicKey( + unlockHotWallet: UnlockHotWallet, + request: DeriveWalletRequest, + ): DerivedPublicKeyResponse = callSdk { derivePublicKey(unlockHotWallet, request) } + + override suspend fun signHashes(unlockHotWallet: UnlockHotWallet, dataToSign: List): List = + callSdk { signHashes(unlockHotWallet, dataToSign) } + + private suspend fun callSdk(block: suspend TangemHotSdk.() -> T): T { + return withTimeout(timeMillis = 1000) { + sdkState.filterNotNull().first() + }.block() + } +} \ No newline at end of file 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 404a978379..9d3861f1db 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 @@ -4,13 +4,11 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.common.keyboard.KeyboardValidator -import com.tangem.common.routing.RoutingFeatureToggle import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.event.TechAnalyticsEvent import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.ui.R import com.tangem.core.ui.coil.ImagePreloader import com.tangem.core.ui.extensions.resourceReference @@ -18,11 +16,15 @@ import com.tangem.core.ui.message.BottomSheetMessage import com.tangem.core.ui.message.EventMessageAction import com.tangem.core.ui.message.SnackbarMessage import com.tangem.datasource.api.common.config.managers.ApiConfigsManager +import com.tangem.datasource.local.config.environment.EnvironmentConfig +import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase import com.tangem.domain.balancehiding.BalanceHidingSettings import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase +import com.tangem.domain.common.LogConfig +import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.notifications.GetApplicationIdUseCase import com.tangem.domain.notifications.SendPushTokenUseCase import com.tangem.domain.notifications.models.ApplicationId @@ -37,15 +39,18 @@ import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase import com.tangem.domain.staking.FetchStakingTokensUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.domain.wallets.usecase.AssociateWalletsWithApplicationIdUseCase import com.tangem.domain.wallets.usecase.GetSavedWalletChangesUseCase import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase import com.tangem.feature.swap.analytics.StoriesEvents -import com.tangem.features.onramp.deeplink.OnrampDeepLink import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog +import com.tangem.tap.network.exchangeServices.ExchangeService +import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService +import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.wallet.BuildConfig @@ -58,7 +63,7 @@ import timber.log.Timber import javax.inject.Inject import kotlin.time.Duration.Companion.seconds -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") @HiltViewModel internal class MainViewModel @Inject constructor( private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase, @@ -78,8 +83,6 @@ internal class MainViewModel @Inject constructor( private val imagePreloader: ImagePreloader, private val fetchHotCryptoUseCase: FetchHotCryptoUseCase, private val onboardingRepository: OnboardingRepository, - private val deepLinksRegistry: DeepLinksRegistry, - private val onrampDeepLinkFactory: OnrampDeepLink.Factory, private val notificationsToggles: NotificationsFeatureToggles, private val getApplicationIdUseCase: GetApplicationIdUseCase, private val subscribeOnWalletsUseCase: GetSavedWalletChangesUseCase, @@ -88,7 +91,8 @@ internal class MainViewModel @Inject constructor( private val sendPushTokenUseCase: SendPushTokenUseCase, private val apiConfigsManager: ApiConfigsManager, private val multiQuoteUpdater: MultiQuoteUpdater, - routingFeatureToggle: RoutingFeatureToggle, + private val appStateHolder: AppStateHolder, + private val environmentConfigStorage: EnvironmentConfigStorage, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel() { @@ -120,6 +124,8 @@ internal class MainViewModel @Inject constructor( multiQuoteUpdater.subscribe() + initializeOffRamp() + observeFlips() displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() @@ -129,10 +135,6 @@ internal class MainViewModel @Inject constructor( sendKeyboardIdentifierEvent() preloadImages() - - if (!routingFeatureToggle.isDeepLinkNavigationEnabled) { - initializeDeepLinks() - } } override fun onCleared() { @@ -189,7 +191,12 @@ internal class MainViewModel @Inject constructor( userWalletsListManager.selectedUserWallet .distinctUntilChanged() .onEach { userWallet -> - Analytics.setContext(userWallet.requireColdWallet().scanResponse) // TODO [REDACTED_TASK_KEY] + when (userWallet) { + is UserWallet.Cold -> Analytics.setContext(userWallet.scanResponse) + is UserWallet.Hot -> { + // TODO [REDACTED_TASK_KEY] + } + } } .flowOn(dispatchers.io) .launchIn(viewModelScope) @@ -201,6 +208,28 @@ internal class MainViewModel @Inject constructor( .onRight { Timber.d("Staking token list was fetched successfully") } } + private fun initializeOffRamp() { + viewModelScope.launch { + val sellService = makeSellExchangeService(environmentConfig = environmentConfigStorage.getConfigSync()) + appStateHolder.sellService = sellService + + sellService.update() + } + } + + private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): ExchangeService { + val cardProvider: () -> ScanResponse? = { + userWalletsListManager.selectedUserWalletSync?.requireColdWallet()?.scanResponse // TODO [REDACTED_TASK_KEY] + } + + return MoonPayService( + apiKey = environmentConfig.moonPayApiKey, + secretKey = environmentConfig.moonPayApiSecretKey, + logEnabled = LogConfig.network.moonPayService, + cardProvider = { cardProvider.invoke()?.card }, + ) + } + private fun observeFlips() { listenToFlipsUseCase().launchIn(viewModelScope) } @@ -384,7 +413,7 @@ internal class MainViewModel @Inject constructor( } } } catch (ex: Exception) { - Timber.e(ex.message) + Timber.e(ex) analyticsEventHandler.send( StoriesEvents.Error( type = StoryContentIds.STORY_FIRST_TIME_SWAP.analyticType, @@ -393,10 +422,6 @@ internal class MainViewModel @Inject constructor( } } - private fun initializeDeepLinks() { - deepLinksRegistry.register(onrampDeepLinkFactory.create(viewModelScope)) - } - private suspend fun initPushNotifications() { if (notificationsToggles.isNotificationsEnabled) { getApplicationIdUseCase().onRight { applicationId -> diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt deleted file mode 100644 index 8ea87b04a4..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.tap.features.onboarding - -import com.tangem.domain.models.scan.ScanResponse - -@Deprecated("Remove when navigation refactoring will be implemented") -class OnboardingManager(var scanResponse: ScanResponse) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt index aa0bf35450..d5851ab0ed 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt @@ -27,7 +27,7 @@ object WalletActivationErrorDialog { // changed on email support [REDACTED_TASK_KEY] Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) - val scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse + val scanResponse = store.state.globalState.scanResponse ?: error("ScanResponse must be not null") val cardInfo = store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull() 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 2e92c8c5f4..d2f1baf710 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 @@ -1,30 +1,18 @@ package com.tangem.tap.features.wallet.redux.middlewares -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchainsdk.utils.toBlockchain -import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.Analytics -import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.NetworkAddress -import com.tangem.domain.onramp.model.OnrampSource import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.demo.DemoHelper -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager -import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens import com.tangem.tap.proxy.redux.DaggerGraphState -import com.tangem.tap.scope import com.tangem.tap.store -import kotlinx.coroutines.launch import org.rekotlin.Middleware @Deprecated("Will be removed soon") @@ -46,79 +34,10 @@ object TradeCryptoMiddleware { when (action) { is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId) - is TradeCryptoAction.Buy -> proceedBuyAction(action) is TradeCryptoAction.Sell -> proceedSellAction(action) } } - private fun proceedBuyAction(action: TradeCryptoAction.Buy) { - val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled - if (isOnrampEnabled) { - proceedWithOnramp(action.userWallet.walletId, action.cryptoCurrencyStatus.currency, action.source) - } else { - proceedWithLegacyBuyAction(action) - } - } - - private fun proceedWithOnramp(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, source: OnrampSource) { - store.dispatchNavigationAction { - push( - AppRoute.Onramp( - userWalletId = userWalletId, - currency = cryptoCurrency, - source = source, - ), - ) - } - } - - private fun proceedWithLegacyBuyAction(action: TradeCryptoAction.Buy) { - val networkAddress = action.cryptoCurrencyStatus.value.networkAddress - ?.defaultAddress - ?.let(NetworkAddress.Address::value) - ?: return - - val status = action.cryptoCurrencyStatus - val currency = status.currency - val blockchain = currency.network.toBlockchain() - val exchangeManager = store.state.globalState.exchangeManager - val topUrl = exchangeManager.getUrl( - action = CurrencyExchangeManager.Action.Buy, - cryptoCurrency = currency, - fiatCurrencyName = action.appCurrencyCode, - walletAddress = networkAddress, - isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive, - ) - - scope.launch { - if (currency is CryptoCurrency.Token && currency.network.isTestnet) { - val walletManager = store.inject(DaggerGraphState::walletManagersFacade) - .getOrCreateWalletManager( - userWalletId = action.userWallet.walletId, - blockchain = blockchain, - derivationPath = currency.network.derivationPath.value, - ) - - if (walletManager !is EthereumWalletManager) { - store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum") - return@launch - } - - buyErc20TestnetTokens( - card = action.userWallet.requireColdWallet().scanResponse.card, // TODO [REDACTED_TASK_KEY] - walletManager = walletManager, - destinationAddress = currency.contractAddress, - ) - return@launch - } - - topUrl?.let { - store.dispatchOpenUrl(it) - Analytics.send(Token.Topup.ScreenOpened()) - } - } - } - private fun proceedSellAction(action: TradeCryptoAction.Sell) { val networkAddress = action.cryptoCurrencyStatus.value.networkAddress ?.defaultAddress @@ -126,8 +45,7 @@ object TradeCryptoMiddleware { ?: return val currency = action.cryptoCurrencyStatus.currency - store.state.globalState.exchangeManager.getUrl( - action = CurrencyExchangeManager.Action.Sell, + store.inject(DaggerGraphState::appStateHolder).sellService?.getUrl( cryptoCurrency = currency, fiatCurrencyName = action.appCurrencyCode, walletAddress = networkAddress, @@ -140,9 +58,9 @@ object TradeCryptoMiddleware { private fun openReceiptUrl(transactionId: String) { store.dispatchNavigationAction(AppRouter::pop) - store.state.globalState.exchangeManager.getSellCryptoReceiptUrl( - action = CurrencyExchangeManager.Action.Sell, - transactionId = transactionId, - )?.let { store.dispatchOpenUrl(it) } + + val sellService = store.inject(DaggerGraphState::appStateHolder).sellService + sellService?.getSellCryptoReceiptUrl(transactionId = transactionId) + ?.let(store::dispatchOpenUrl) } } \ No newline at end of file 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 45fc140480..8b1dc10be1 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 @@ -131,6 +131,5 @@ internal class WelcomeModel @Inject constructor( private fun initGlobalState() { store.dispatch(GlobalAction.RestoreAppCurrency) - store.dispatch(GlobalAction.ExchangeManager.Init) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt index 2820d0ed44..e56bec1fd1 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt @@ -9,13 +9,23 @@ import com.tangem.domain.wallets.models.requireColdWallet internal class DefaultAuthProvider(private val userWalletsListManager: UserWalletsListManager) : AuthProvider { override fun getCardPublicKey(): String { - return userWalletsListManager.selectedUserWalletSync - ?.requireColdWallet()?.scanResponse?.card?.cardPublicKey?.toHexString() ?: "" + val userWallet = userWalletsListManager.selectedUserWalletSync + + return when (userWallet) { + is UserWallet.Cold -> userWallet.scanResponse.card.cardPublicKey.toHexString() + is UserWallet.Hot -> userWallet.wallets?.firstOrNull()?.publicKey?.toHexString() ?: "" + null -> "" + } } override fun getCardId(): String { - return userWalletsListManager.selectedUserWalletSync - ?.requireColdWallet()?.scanResponse?.card?.cardId ?: "" + val userWallet = userWalletsListManager.selectedUserWalletSync + + if (userWallet !is UserWallet.Cold) { + return "" + } + + return userWallet.requireColdWallet().scanResponse.card.cardId } override fun getCardsPublicKeys(): Map { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt deleted file mode 100644 index 54a3a0b6d6..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.tap.network.exchangeServices - -import com.tangem.domain.common.TapWorkarounds.isStart2Coin -import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.domain.model.Currency -import com.tangem.tap.features.demo.isDemoCard - -/** -[REDACTED_AUTHOR] - */ -class CardExchangeRules( - val cardProvider: () -> CardDTO?, -) : ExchangeRules { - - override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean { - val card = scanResponse.card - - return when { - card.isDemoCard() -> true - card.isStart2Coin -> false - else -> true - } - } - - override fun availableForSell(currency: Currency): Boolean { - val card = cardProvider() ?: return false - - return !card.isStart2Coin - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt deleted file mode 100644 index 83fee90f6c..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt +++ /dev/null @@ -1,148 +0,0 @@ -package com.tangem.tap.network.exchangeServices - -import com.tangem.Message -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.blockchain.extensions.Result -import com.tangem.blockchainsdk.utils.toBlockchain -import com.tangem.domain.common.TapWorkarounds.isTangemTwins -import com.tangem.domain.core.utils.lceContent -import com.tangem.domain.core.utils.lceLoading -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.common.extensions.inject -import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.TangemSigner -import com.tangem.tap.domain.model.Currency -import com.tangem.tap.features.demo.isDemoCard -import com.tangem.tap.proxy.redux.DaggerGraphState -import com.tangem.tap.store -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -class CurrencyExchangeManager( - private val buyService: ExchangeService, - private val sellService: ExchangeService, - private val primaryRules: ExchangeRules, -) : ExchangeService { - - override val initializationStatus: StateFlow - get() = _initializationStatus - - private val _initializationStatus: MutableStateFlow = - MutableStateFlow(value = lceLoading()) - - override suspend fun update() { - _initializationStatus.value = lceLoading() - - if (!store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled) { - buyService.update() - } - - sellService.update() - - _initializationStatus.value = lceContent() - } - - override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean { - return primaryRules.availableForBuy(scanResponse, currency) && - buyService.availableForBuy(scanResponse, currency) - } - - override fun availableForSell(currency: Currency): Boolean { - return primaryRules.availableForSell(currency) && sellService.availableForSell(currency) - } - - override fun getUrl( - action: Action, - cryptoCurrency: CryptoCurrency, - fiatCurrencyName: String, - walletAddress: String, - isDarkTheme: Boolean, - ): String? { - val blockchain = cryptoCurrency.network.toBlockchain() - if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl() - - val urlBuilder = getExchangeUrlBuilder(action) - return urlBuilder.getUrl( - action, - cryptoCurrency, - fiatCurrencyName, - walletAddress, - isDarkTheme, - ) - } - - override fun getSellCryptoReceiptUrl(action: Action, transactionId: String): String? { - val urlBuilder = getExchangeUrlBuilder(action) - return urlBuilder.getSellCryptoReceiptUrl(action, transactionId) - } - - private fun getExchangeUrlBuilder(action: Action): ExchangeUrlBuilder { - return when (action) { - Action.Buy -> buyService - Action.Sell -> sellService - } - } - - enum class Action { Buy, Sell } - - companion object { - fun dummy(): CurrencyExchangeManager = CurrencyExchangeManager( - buyService = ExchangeService.dummy(), - sellService = ExchangeService.dummy(), - primaryRules = ExchangeRules.dummy(), - ) - } -} - -@Deprecated("Useless") -suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletManager, destinationAddress: String) { - walletManager.safeUpdate(card.isDemoCard()) - - val amountToSend = Amount(walletManager.wallet.blockchain) - - val feeResult = walletManager.getFee(amountToSend, destinationAddress) as? Result.Success ?: return - val fee = when (val feeForTx = feeResult.data) { - is TransactionFee.Choosable -> feeForTx.minimum - is TransactionFee.Single -> feeForTx.normal - } - - val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO - if (coinValue < fee.amount.value) return - - val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins - val signer = TangemSigner( - cardId = card.cardId.takeIf { isCardNotBackedUp }, - tangemSdk = store.inject(DaggerGraphState::cardSdkConfigRepository).sdk, - initialMessage = Message(), - twinKey = null, - ) { signResponse -> - store.dispatch( - GlobalAction.UpdateWalletSignedHashes( - walletSignedHashes = signResponse.totalSignedHashes, - walletPublicKey = walletManager.wallet.publicKey.seedKey, - remainingSignatures = signResponse.remainingSignatures, - ), - ) - - store.dispatch(action = GlobalAction.IsSignWithRing(signResponse.isRing)) - } - - walletManager.send( - transactionData = walletManager.createTransaction( - amount = amountToSend, - fee = fee, - destination = destinationAddress, - ), - signer = signer, - ) -} \ No newline at end of file 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 6527ffcacc..9b97bcafce 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 @@ -18,7 +18,6 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching @@ -28,13 +27,10 @@ import kotlinx.coroutines.flow.firstOrNull @Suppress("LongParameterList") internal class DefaultRampManager( - private val exchangeService: ExchangeService?, - private val buyService: Provider, private val sellService: Provider, private val expressServiceLoader: ExpressServiceLoader, private val currenciesRepository: CurrenciesRepository, private val dispatchers: CoroutineDispatcherProvider, - private val onrampFeatureToggles: OnrampFeatureToggles, excludedBlockchains: ExcludedBlockchains, ) : RampStateManager { @@ -45,9 +41,9 @@ internal class DefaultRampManager( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, ): ScenarioUnavailabilityReason { - val availabilityState = runCatching { - getOnrampAvailableState(scanResponse, userWalletId, cryptoCurrency) - }.getOrNull() ?: ExpressAvailabilityState.Error + val availabilityState = runCatching { getOnrampAvailableState(userWalletId, cryptoCurrency) } + .getOrNull() + ?: ExpressAvailabilityState.Error return availabilityState.toReason(cryptoCurrency.name) } @@ -62,7 +58,7 @@ internal class DefaultRampManager( block = { val serviceCurrency = cryptoCurrencyConverter.convertBack(status.currency) - exchangeService?.availableForSell(currency = serviceCurrency) == true + sellService().availableForSell(currency = serviceCurrency) }, catch = { raise(ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name)) }, ) @@ -100,16 +96,6 @@ internal class DefaultRampManager( return availabilityState.toReason(cryptoCurrency.name) } - override fun getBuyInitializationStatus(): Flow { - return buyService.invoke().initializationStatus - } - - override suspend fun fetchBuyServiceData() { - runCatching(dispatchers.io) { - buyService.invoke().update() - } - } - override fun getSellInitializationStatus(): Flow { return sellService.invoke().initializationStatus } @@ -163,31 +149,20 @@ internal class DefaultRampManager( } private suspend fun getOnrampAvailableState( - scanResponse: ScanResponse, userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, ): ExpressAvailabilityState { - return when { - onrampFeatureToggles.isFeatureEnabled -> { - val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull() - ?: return ExpressAvailabilityState.Loading - return when (asset) { - is Lce.Error -> ExpressAvailabilityState.Error - is Lce.Loading -> ExpressAvailabilityState.Loading - is Lce.Content -> { - val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) } - foundAsset?.onrampAvailable?.toOnrampAvailabilityState() - ?: ExpressAvailabilityState.AssetNotFound - } - } + val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull() + ?: return ExpressAvailabilityState.Loading + + return when (asset) { + is Lce.Error -> ExpressAvailabilityState.Error + is Lce.Loading -> ExpressAvailabilityState.Loading + is Lce.Content -> { + val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) } + foundAsset?.onrampAvailable?.toOnrampAvailabilityState() + ?: ExpressAvailabilityState.AssetNotFound } - exchangeService != null -> { - exchangeService.availableForBuy( - scanResponse = scanResponse, - currency = cryptoCurrencyConverter.convertBack(cryptoCurrency), - ).toOnrampAvailabilityState() - } - else -> ExpressAvailabilityState.Error } } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt index 1641baa48f..88ba965edc 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt @@ -1,75 +1,26 @@ package com.tangem.tap.network.exchangeServices import com.tangem.domain.core.lce.Lce -import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.domain.model.Currency -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow typealias ExchangeServiceInitializationStatus = Lce -interface Exchanger { - fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean - fun availableForSell(currency: Currency): Boolean -} - -interface ExchangeService : Exchanger, ExchangeUrlBuilder { +interface ExchangeService { val initializationStatus: StateFlow suspend fun update() - companion object { - fun dummy(): ExchangeService = object : ExchangeService { + fun availableForSell(currency: Currency): Boolean - override val initializationStatus: StateFlow = - MutableStateFlow(value = lceLoading()) - - override suspend fun update() {} - override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false - override fun availableForSell(currency: Currency): Boolean = false - override fun getUrl( - action: CurrencyExchangeManager.Action, - cryptoCurrency: CryptoCurrency, - fiatCurrencyName: String, - walletAddress: String, - isDarkTheme: Boolean, - ): String? = null - - override fun getSellCryptoReceiptUrl( - action: CurrencyExchangeManager.Action, - transactionId: String, - ): String? = null - } - } -} - -interface ExchangeRules : Exchanger { - - companion object { - fun dummy(): ExchangeRules = object : ExchangeRules { - override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false - override fun availableForSell(currency: Currency): Boolean = false - } - } -} - -interface ExchangeUrlBuilder { - @Suppress("LongParameterList") fun getUrl( - action: CurrencyExchangeManager.Action, cryptoCurrency: CryptoCurrency, fiatCurrencyName: String, walletAddress: String, isDarkTheme: Boolean, ): String? - fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? - - companion object { - const val SCHEME = "https" - const val SUCCESS_URL = "https://tangem.com/success" - } + fun getSellCryptoReceiptUrl(transactionId: String): String? } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt deleted file mode 100644 index 6709842bb5..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.tangem.tap.network.exchangeServices.mercuryo - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import retrofit2.http.GET -import retrofit2.http.Path - - -interface MercuryoApi { - - @GET("{apiVersion}/lib/currencies") - suspend fun currencies(@Path("apiVersion") apiVersion: String): MercuryoCurrenciesResponse -} - -@JsonClass(generateAdapter = true) -data class MercuryoCurrenciesResponse( - @Json(name = "status") val status: Int, - @Json(name = "data") val data: Data, -) { - @JsonClass(generateAdapter = true) - data class Data( - @Json(name = "fiat") val fiat: List, - @Json(name = "crypto") val crypto: List, - @Json(name = "config") val config: Config, - ) - - @JsonClass(generateAdapter = true) - data class Config( - @Json(name = "crypto_currencies") - val cryptoCurrencies: List, - ) - - @JsonClass(generateAdapter = true) - data class MercuryoCryptoCurrency( - @Json(name = "currency") - val currencySymbol: String, - @Json(name = "network") - val network: String, - @Json(name = "contract") - val contractAddress: String, - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoEnvironment.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoEnvironment.kt deleted file mode 100644 index 36fd477bc5..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoEnvironment.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.tap.network.exchangeServices.mercuryo - -import com.tangem.datasource.api.common.createRetrofitInstance - -/** -[REDACTED_AUTHOR] - */ -data class MercuryoEnvironment( - val baseUrl: String, - val apiVersion: String, - val widgetId: String, - val secret: String, - val mercuryoApi: MercuryoApi, -) { - companion object { - private const val BASE_URL = "https://api.mercuryo.io/" - private const val API_VERSION = "v1.6" - - fun prod(widgetId: String, secret: String, apiVersion: String = API_VERSION): MercuryoEnvironment { - return MercuryoEnvironment( - baseUrl = BASE_URL, - apiVersion = apiVersion, - widgetId = widgetId, - secret = secret, - mercuryoApi = createRetrofitInstance( - baseUrl = BASE_URL, - logEnabled = false, - ).create(MercuryoApi::class.java), - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt deleted file mode 100644 index 16219f4094..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ /dev/null @@ -1,116 +0,0 @@ -package com.tangem.tap.network.exchangeServices.mercuryo - -import android.net.Uri -import com.tangem.blockchainsdk.utils.toBlockchain -import com.tangem.common.extensions.calculateSha512 -import com.tangem.common.extensions.toHexString -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest -import com.tangem.data.onramp.legacy.mercuryoNetwork -import com.tangem.domain.core.utils.lceContent -import com.tangem.domain.core.utils.lceError -import com.tangem.domain.core.utils.lceLoading -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.domain.model.Currency -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager -import com.tangem.tap.network.exchangeServices.ExchangeService -import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus -import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import timber.log.Timber -import java.util.concurrent.CopyOnWriteArrayList - -/** -[REDACTED_AUTHOR] - */ -internal class MercuryoService(private val environment: MercuryoEnvironment) : ExchangeService { - - override val initializationStatus: StateFlow - get() = _initializationStatus - - private val _initializationStatus: MutableStateFlow = - MutableStateFlow(value = lceLoading()) - - private val api: MercuryoApi = environment.mercuryoApi - - private val availableMercuryoCurrencies = CopyOnWriteArrayList() - - override suspend fun update() { - Timber.i("Start updating") - _initializationStatus.value = lceLoading() - - val result = performRequest { api.currencies(environment.apiVersion) } - when { - result is Result.Success && result.data.status == RESPONSE_SUCCESS_STATUS_CODE -> { - handleSuccessfullyUpdatedData(data = result.data.data) - } - result is Result.Failure -> { - availableMercuryoCurrencies.clear() - - Timber.e("Failed to load currencies", result.error) - _initializationStatus.value = result.error.lceError() - } - } - } - - override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean { - val mercuryoNetwork = currency.blockchain.mercuryoNetwork - val contractAddress = (currency as? Currency.Token)?.token?.contractAddress ?: "" - val availableCurrency = availableMercuryoCurrencies.firstOrNull { - it.currencySymbol == currency.currencySymbol && - it.network == mercuryoNetwork && - it.contractAddress.equals(contractAddress, ignoreCase = true) - } - return availableCurrency != null - } - - override fun availableForSell(currency: Currency): Boolean = false - - override fun getUrl( - action: CurrencyExchangeManager.Action, - cryptoCurrency: CryptoCurrency, - fiatCurrencyName: String, - walletAddress: String, - isDarkTheme: Boolean, - ): String { - if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException() - - val blockchain = cryptoCurrency.network.toBlockchain() - - val builder = Uri.Builder() - .scheme(ExchangeUrlBuilder.SCHEME) - .authority("exchange.mercuryo.io") - .appendQueryParameter("widget_id", environment.widgetId) - .appendQueryParameter("type", action.name.lowercase()) - .appendQueryParameter("currency", cryptoCurrency.symbol) - .appendQueryParameter("address", walletAddress) - .appendQueryParameter("signature", signature(walletAddress)) - .appendQueryParameter("fix_currency", "true") - .appendQueryParameter("redirect_url", ExchangeUrlBuilder.SUCCESS_URL) - if (isDarkTheme) builder.appendQueryParameter("theme", "1inch") - - blockchain.mercuryoNetwork?.let { - builder.appendQueryParameter("network", it) - } - - return builder.build().toString() - } - - override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null - - private fun handleSuccessfullyUpdatedData(data: MercuryoCurrenciesResponse.Data) { - availableMercuryoCurrencies.clear() - availableMercuryoCurrencies.addAll(data.config.cryptoCurrencies) - - Timber.i("Successfully updated") - _initializationStatus.value = lceContent() - } - - private fun signature(address: String) = (address + environment.secret).calculateSha512().toHexString().lowercase() - - private companion object { - const val RESPONSE_SUCCESS_STATUS_CODE = 200 - } -} \ No newline at end of file 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 d57da1a257..36b06ad6e9 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 @@ -6,17 +6,16 @@ import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.common.services.Result import com.tangem.common.services.performRequest import com.tangem.datasource.api.common.createRetrofitInstance +import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withIOContext import com.tangem.domain.core.utils.lceContent import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.scan.CardDTO import com.tangem.tap.domain.model.Currency -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.network.exchangeServices.ExchangeService import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus -import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPayAvailableCurrency import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -28,6 +27,7 @@ class MoonPayService( private val apiKey: String, private val secretKey: String, private val logEnabled: Boolean, + private val cardProvider: () -> CardDTO?, ) : ExchangeService { override val initializationStatus: StateFlow @@ -102,9 +102,12 @@ class MoonPayService( } } - override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false - override fun availableForSell(currency: Currency): Boolean { + val card = cardProvider() ?: return false + val checkCardExchange = !card.isStart2Coin + + if (!checkCardExchange) return false + if (!isSellAllowed()) return false val availableForSell = status?.availableForSell ?: return false @@ -125,15 +128,14 @@ class MoonPayService( } override fun getUrl( - action: CurrencyExchangeManager.Action, cryptoCurrency: CryptoCurrency, fiatCurrencyName: String, walletAddress: String, isDarkTheme: Boolean, ): String? { - if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException() - val blockchain = cryptoCurrency.network.toBlockchain() + if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl() + val supportedCurrency = blockchain.moonPaySupportedCurrency ?: return null val moonpayCurrency = status?.availableForSell?.firstOrNull { when (cryptoCurrency) { @@ -165,7 +167,7 @@ class MoonPayService( return uri.build().toString() } - override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String { + override fun getSellCryptoReceiptUrl(transactionId: String): String { return Uri.Builder() .scheme(SCHEME) .authority(URL_SELL) @@ -185,8 +187,9 @@ class MoonPayService( return status?.responseUserStatus?.isSellAllowed ?: false } - companion object { + private companion object { const val URL_SELL = "sell.moonpay.com" + const val SCHEME = "https" } } diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index e45195044a..636647d8fc 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -19,8 +19,6 @@ import javax.inject.Inject class AppStateHolder @Inject constructor() : ReduxStateHolder { var mainStore: Store? = null - var exchangeService: ExchangeService? = null - var buyService: ExchangeService? = null var sellService: ExchangeService? = null override fun dispatch(action: Action) { 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 e7817d2245..1c696ac842 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 @@ -32,9 +32,7 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles -import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.operations.attestation.CardArtworksProvider -import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor @@ -68,7 +66,6 @@ data class DaggerGraphState( val shareManager: ShareManager? = null, val appRouter: AppRouter? = null, val transactionSignerFactory: TransactionSignerFactory? = null, - val onrampFeatureToggles: OnrampFeatureToggles? = null, val environmentConfigStorage: EnvironmentConfigStorage? = null, val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null, val onboardingRepository: OnboardingRepository? = null, @@ -77,7 +74,6 @@ data class DaggerGraphState( val clipboardManager: ClipboardManager? = null, val settingsManager: SettingsManager? = null, val uiMessageSender: UiMessageSender? = null, - val onlineCardVerifier: OnlineCardVerifier? = null, val cardArworksProvider: CardArtworksProvider? = null, val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory? = null, val userTokensResponseStore: UserTokensResponseStore? = null, diff --git a/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt b/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt index cdebe55b4a..492ca42a8b 100644 --- a/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt +++ b/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt @@ -75,6 +75,7 @@ internal class ProxyAppRouter( try { block() } catch (e: Throwable) { + Timber.e(e) onComplete(false) } } 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 64d10c0193..909be69b49 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 @@ -6,6 +6,7 @@ import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.subscribe +import com.arkivanov.essenty.lifecycle.subscribe import com.google.android.material.snackbar.Snackbar import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.context.AppComponentContext @@ -16,7 +17,10 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.SnackbarMessage import com.tangem.features.walletconnect.components.WcRoutingComponent +import com.tangem.hot.sdk.TangemHotSdk +import com.tangem.hot.sdk.android.create import com.tangem.tap.common.SnackbarHandler +import com.tangem.tap.features.hot.TangemHotSDKProxy import com.tangem.tap.routing.RootContent import com.tangem.tap.routing.component.RoutingComponent import com.tangem.tap.routing.component.RoutingComponent.Child @@ -36,6 +40,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val uiDependencies: UiDependencies, private val wcRoutingComponentFactory: WcRoutingComponent.Factory, private val deeplinkFactory: DeepLinkFactory, + private val tangemHotSDKProxy: TangemHotSDKProxy, ) : RoutingComponent, AppComponentContext by context, SnackbarHandler { @@ -70,6 +75,8 @@ internal class DefaultRoutingComponent @AssistedInject constructor( appRouterConfig.stack = stackItems } } + + configureHotSdk() } @Composable @@ -120,6 +127,17 @@ internal class DefaultRoutingComponent @AssistedInject constructor( initialStack } + private fun configureHotSdk() { + lifecycle.subscribe( + onCreate = { + tangemHotSDKProxy.sdkState.value = TangemHotSdk.create(activity) + }, + onDestroy = { + tangemHotSDKProxy.sdkState.value = null + }, + ) + } + @AssistedFactory interface Factory : RoutingComponent.Factory { override fun create(context: AppComponentContext, initialStack: List?): DefaultRoutingComponent 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 825a388842..2e75544715 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 @@ -26,7 +26,6 @@ import com.tangem.features.send.v2.api.NFTSendComponent import com.tangem.features.send.v2.api.SendComponent import com.tangem.features.staking.api.StakingComponent import com.tangem.features.swap.SwapComponent -import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.wallet.WalletEntryComponent import com.tangem.features.walletconnect.components.WalletConnectEntryComponent @@ -86,7 +85,6 @@ internal class ChildFactory @Inject constructor( private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory, private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory, private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory, - private val testerRouter: TesterRouter, private val walletConnectFeatureToggles: WalletConnectFeatureToggles, ) { @@ -132,9 +130,6 @@ internal class ChildFactory @Inject constructor( componentFactory = welcomeComponentFactory, ) } - is AppRoute.TesterMenu -> { - Child.LegacyIntent(testerRouter.getEntryIntent()) - } is AppRoute.WalletSettings -> { createComponentChild( context = context, diff --git a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt index 798ad3a70f..436d1a2ef3 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt @@ -8,7 +8,10 @@ import com.tangem.data.card.sdk.CardSdkProvider import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler -import com.tangem.features.onramp.deeplink.* +import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler +import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler +import com.tangem.features.onramp.deeplink.SellDeepLinkHandler +import com.tangem.features.onramp.deeplink.SwapDeepLinkHandler import com.tangem.features.send.v2.api.deeplink.SellRedirectDeepLinkHandler import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler @@ -33,7 +36,6 @@ internal class DeepLinkFactory @Inject constructor( private val cardSdkProvider: CardSdkProvider, private val onrampDeepLink: OnrampDeepLinkHandler.Factory, private val sellRedirectDeepLink: SellRedirectDeepLinkHandler.Factory, - private val buyRedirectDeepLink: BuyRedirectDeepLinkHandler.Factory, private val referralDeepLink: ReferralDeepLinkHandler.Factory, private val walletConnectDeepLink: WalletConnectDeepLinkHandler.Factory, private val walletDeepLink: WalletDeepLinkHandler.Factory, @@ -114,7 +116,7 @@ internal class DeepLinkFactory @Inject constructor( when (deeplinkUri.host) { DeepLinkRoute.Onramp.host -> onrampDeepLink.create(coroutineScope, queryParams) DeepLinkRoute.SellRedirect.host -> sellRedirectDeepLink.create(coroutineScope, queryParams) - DeepLinkRoute.BuyRedirect.host -> buyRedirectDeepLink.create(coroutineScope) + DeepLinkRoute.BuyRedirect.host -> Unit DeepLinkRoute.Referral.host -> referralDeepLink.create() DeepLinkRoute.Wallet.host -> walletDeepLink.create() DeepLinkRoute.TokenDetails.host -> tokenDetailsDeepLink.create( diff --git a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt index 018f3b76b8..34c9ae969c 100644 --- a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt @@ -6,7 +6,10 @@ import com.tangem.data.card.sdk.CardSdkProvider import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler -import com.tangem.features.onramp.deeplink.* +import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler +import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler +import com.tangem.features.onramp.deeplink.SellDeepLinkHandler +import com.tangem.features.onramp.deeplink.SwapDeepLinkHandler import com.tangem.features.send.v2.api.deeplink.SellRedirectDeepLinkHandler import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler @@ -34,9 +37,6 @@ class DeepLinkFactoryTest { private val sellRedirectDeepLinkFactory = mockk(relaxed = true) { every { create(any(), any()) } returns mockk() } - private val buyRedirectDeepLinkFactory = mockk(relaxed = true) { - every { create(any()) } returns mockk() - } private val referralDeepLinkFactory = mockk(relaxed = true) { every { create() } returns mockk() } @@ -81,7 +81,6 @@ class DeepLinkFactoryTest { cardSdkProvider = cardSdkProvider, onrampDeepLink = onrampDeepLinkFactory, sellRedirectDeepLink = sellRedirectDeepLinkFactory, - buyRedirectDeepLink = buyRedirectDeepLinkFactory, referralDeepLink = referralDeepLinkFactory, walletConnectDeepLink = walletConnectDeepLinkFactory, walletDeepLink = walletDeepLinkFactory, @@ -220,7 +219,6 @@ class DeepLinkFactoryTest { verify(inverse = true) { onrampDeepLinkFactory.create(any(), any()) sellRedirectDeepLinkFactory.create(any(), any()) - buyRedirectDeepLinkFactory.create(any()) referralDeepLinkFactory.create() walletConnectDeepLinkFactory.create(any()) walletDeepLinkFactory.create() @@ -277,12 +275,6 @@ class DeepLinkFactoryTest { every { mockedUri.queryParameterNames } returns emptySet() every { mockedUri.getQueryParameter(any()) } returns "" - // Test Buy Redirect - every { mockedUri.host } returns "redirect" - deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) - advanceUntilIdle() - verify { buyRedirectDeepLinkFactory.create(eq(testScope)) } - // Test Referral every { mockedUri.host } returns "referral" deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent) @@ -333,7 +325,6 @@ class DeepLinkFactoryTest { verify(inverse = true) { onrampDeepLinkFactory.create(any(), any()) sellRedirectDeepLinkFactory.create(any(), any()) - buyRedirectDeepLinkFactory.create(any()) referralDeepLinkFactory.create() walletConnectDeepLinkFactory.create(any()) walletDeepLinkFactory.create() @@ -348,11 +339,10 @@ class DeepLinkFactoryTest { fun `getParams filters malicious parameters`() = runTest { every { mockedUri.scheme } returns "tangem" every { mockedUri.host } returns "onramp" - every { mockedUri.query } returns "safe=ok&malicious=%3Cscript%3E"e=O%27Brien" - every { mockedUri.queryParameterNames } returns setOf("safe", "malicious", "quote") + every { mockedUri.query } returns "safe=ok&malicious=%3Cscript%3E" + every { mockedUri.queryParameterNames } returns setOf("safe", "malicious") every { mockedUri.getQueryParameter("safe") } returns "ok" every { mockedUri.getQueryParameter("malicious") } returns "