diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6502728cd8..037d6c2a83 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -134,7 +134,6 @@ dependencies { implementation(projects.core.datasource) implementation(projects.core.utils) implementation(projects.core.decompose) - implementation(projects.core.deepLinks) implementation(projects.core.error.ext) implementation(projects.libs.crypto) implementation(projects.libs.auth) @@ -281,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) @@ -300,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..d42aeddab4 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -1,7 +1,10 @@ package com.tangem.common import android.Manifest -import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.junit4.createEmptyComposeRule +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.printToLog +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 +13,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 +38,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,13 +59,22 @@ 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 { @@ -67,4 +82,21 @@ abstract class BaseTestCase : TestCase( Intents.release() } + /** + * Prints the Compose semantics tree to logcat for debugging UI tests. + * + * @param useUnmergedTree When true, shows unmerged tree with all individual nodes. + * Use for accessing inner elements of compound components. + * Default: false (merged tree - accessibility view). + * @param tag Log tag for filtering in logcat. Default: "SEMANTIC_TREE". + * @param maxDepth Maximum nesting level to print. Use to avoid log overflow. + * Default: Int.MAX_VALUE (unlimited depth). + */ + fun printSemanticTree( + useUnmergedTree: Boolean = false, + tag: String = "SEMANTIC_TREE", + maxDepth: Int = Int.MAX_VALUE) + { + composeTestRule.onRoot(useUnmergedTree = useUnmergedTree).printToLog(tag, maxDepth) + } } \ 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/extensions/SemanticMatchers.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/SemanticMatchers.kt new file mode 100644 index 0000000000..4bf468227f --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/SemanticMatchers.kt @@ -0,0 +1,9 @@ +package com.tangem.common.extensions + +import androidx.compose.ui.test.SemanticsMatcher +import com.tangem.core.ui.utils.LazyListItemPositionSemantics +import io.github.kakaocup.compose.node.builder.ViewBuilder + +fun ViewBuilder.hasLazyListItemPosition(position: Int) = apply { + addSemanticsMatcher(SemanticsMatcher.expectValue(LazyListItemPositionSemantics, position)) +} \ 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/common/utils/LazyListItemNode.kt b/app/src/androidTest/kotlin/com/tangem/common/utils/LazyListItemNode.kt new file mode 100644 index 0000000000..1dfc811901 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/utils/LazyListItemNode.kt @@ -0,0 +1,14 @@ +package com.tangem.common.utils + +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import io.github.kakaocup.compose.node.element.lazylist.KLazyListItemNode + +/** + * Represents an item node in a LazyList for Compose UI testing. + * Allows test actions (clicks, scrolls) and assertions on individual list items. + */ +class LazyListItemNode( + semanticsNode: SemanticsNode, + semanticsProvider: SemanticsNodeInteractionsProvider, +) : KLazyListItemNode(semanticsNode, semanticsProvider) \ 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..dd160f1b25 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,24 +17,24 @@ 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() + contentContainer.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/DialogPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/DialogPageObject.kt new file mode 100644 index 0000000000..960b371248 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/DialogPageObject.kt @@ -0,0 +1,31 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.R +import com.tangem.core.ui.test.DialogTestTags +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 DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val dialogContainer: KNode = child { + hasTestTag(DialogTestTags.DIALOG_CONTAINER) + } + + val cancelButton: KNode = child { + hasTestTag(DialogTestTags.BUTTON) + hasText(getResourceString(R.string.common_cancel)) + } + + val hideButton: KNode = child { + hasTestTag(DialogTestTags.BUTTON) + hasText(getResourceString(R.string.token_details_hide_alert_hide)) + } +} + +internal fun BaseTestCase.onDialog(function: DialogPageObject.() -> 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..c9a08a06f8 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt @@ -0,0 +1,119 @@ +package com.tangem.screens + +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.hasLazyListItemPosition +import com.tangem.common.utils.LazyListItemNode +import com.tangem.core.ui.test.TokenElementsTestTags +import com.tangem.core.ui.test.MainScreenTestTags +import com.tangem.core.ui.utils.LazyListItemPositionSemantics +import com.tangem.feature.wallet.impl.R +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode +import io.github.kakaocup.kakao.common.utilities.getResourceString +import androidx.compose.ui.test.hasTestTag as withTestTag +import androidx.compose.ui.test.hasText as withText + +class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + private val lazyList = KLazyListNode( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(MainScreenTestTags.SCREEN_CONTAINER) }, + itemTypeBuilder = { + itemType(::LazyListItemNode) + }, + positionMatcher = { position -> + SemanticsMatcher.expectValue( + LazyListItemPositionSemantics, + position + ) + } + ) + + val synchronizeAddressesButton: KNode = child { + hasText(getResourceString(R.string.common_generate_addresses)) + } + + /** + * Find token list item with title and address + */ + @OptIn(ExperimentalTestApi::class) + fun tokenWithTitleAndAddress(tokenTitle: String): KNode { + return lazyList.childWith { + hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) + hasText(tokenTitle) + }.child { + hasTestTag(TokenElementsTestTags.TOKEN_FIAT_AMOUNT) + useUnmergedTree = true + } + } + + /** + * Find node with wallet balance using lazyList. This construction doesn't affect next step with lazyList. + */ + @OptIn(ExperimentalTestApi::class) + fun walletBalance(): KNode { + return lazyList.childWith { + hasAnyDescendant(withTestTag(MainScreenTestTags.WALLET_LIST_ITEM)) + }.child { + hasTestTag(MainScreenTestTags.WALLET_BALANCE) + useUnmergedTree = true + } + } + + @OptIn(ExperimentalTestApi::class) + fun organizeTokensButton(): KNode { + return lazyList.childWith { + hasTestTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON) + }.child { + hasText(getResourceString(R.string.organize_tokens_title)) + useUnmergedTree = true + } + } + + fun tokenNetworkGroupTitle(tokenNetwork: String): KNode { + return lazyList.child { + hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) + hasAnyChild(withText(tokenNetwork)) + useUnmergedTree = true + } + } + + @OptIn(ExperimentalTestApi::class) + fun tokenWithTitleAndPosition(tokenTitle: String, index: Int): KNode { + return lazyList.childWith { + hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM) + hasText(tokenTitle) + hasLazyListItemPosition(index) + }.child { + hasTestTag(TokenElementsTestTags.TOKEN_TITLE) + useUnmergedTree = true + } + } + + + /** + * This assertion is required to properly verify the token's absence in the semantic tree. + * Tests will fail if assertIsNotDisplayed() or assertDoesNotExist() are used instead. + */ + fun assertTokenDoesNotExist(tokenTitle: String) { + try { + tokenWithTitleAndAddress(tokenTitle).assertExists() + throw AssertionError("Token with title '$tokenTitle' should not exist but was found") + } catch (e: AssertionError) { + if (e.message?.contains("No node found") == true) { + return + } else { + throw e + } + } + } +} + +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/MarketsTooltipPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/MarketsTooltipPageObject.kt new file mode 100644 index 0000000000..08ae1af7d8 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/MarketsTooltipPageObject.kt @@ -0,0 +1,19 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.MarketTooltipTestTags +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 MarketsTooltipPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val contentContainer: KNode = child { + hasTestTag(MarketTooltipTestTags.CONTAINER) + } +} + +internal fun BaseTestCase.onMarketsTooltipScreen(function: MarketsTooltipPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/OrganizeTokensPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/OrganizeTokensPageObject.kt new file mode 100644 index 0000000000..f7a76181e8 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/OrganizeTokensPageObject.kt @@ -0,0 +1,113 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.hasLazyListItemPosition +import com.tangem.common.utils.LazyListItemNode +import com.tangem.core.ui.test.TokenElementsTestTags +import com.tangem.core.ui.test.OrganizeTokensScreenTestTags +import com.tangem.core.ui.utils.LazyListItemPositionSemantics +import com.tangem.feature.wallet.impl.R +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode +import io.github.kakaocup.kakao.common.utilities.getResourceString +import androidx.compose.ui.test.hasText as withText +import androidx.compose.ui.test.hasTestTag as withTestTag +import androidx.compose.ui.test.hasAnySibling as withAnySibling +import androidx.compose.ui.test.hasAnyChild as withAnyChild + +class OrganizeTokensPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + // region TopBar + val title: KNode = child { + hasText(getResourceString(R.string.organize_tokens_title)) + } + + private val topBarGroupButton: KNode = child { + hasTestTag(OrganizeTokensScreenTestTags.GROUP_BUTTON) + } + + val groupButton: KNode = topBarGroupButton.child { + hasText(getResourceString(R.string.organize_tokens_group)) + useUnmergedTree = true + } + + val ungroupButton: KNode = topBarGroupButton.child { + hasText(getResourceString(R.string.organize_tokens_ungroup)) + useUnmergedTree = true + } + + val sortByBalanceButton: KNode = child { + hasTestTag(OrganizeTokensScreenTestTags.SORT_BY_BALANCE_BUTTON) + useUnmergedTree = true + } + // endregion TopBar + + private val lazyList = KLazyListNode( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST) }, + itemTypeBuilder = { + itemType(::LazyListItemNode) + }, + positionMatcher = { position -> + SemanticsMatcher.expectValue( + LazyListItemPositionSemantics, + position + ) + } + ) + + val applyButton: KNode = child { + hasTestTag(OrganizeTokensScreenTestTags.APPLY_BUTTON) + useUnmergedTree = true + } + + val cancelButton: KNode = child { + hasTestTag(OrganizeTokensScreenTestTags.CANCEL_BUTTON) + useUnmergedTree = true + } + + fun tokenWithTitle(tokenTitle: String): KNode { + return lazyList.child { + hasTestTag(OrganizeTokensScreenTestTags.TOKEN_LIST_ITEM) + hasAnyDescendant(withText(tokenTitle)) + useUnmergedTree = true + } + } + + fun tokenNetworkGroupTitle(tokenNetwork: String): KNode { + return lazyList.child { + hasTestTag(OrganizeTokensScreenTestTags.GROUP_TITLE_ITEM) + hasAnyChild(withText(tokenNetwork)) + useUnmergedTree = true + } + } + + fun tokenWithTitleAndPosition(tokenTitle: String, index: Int): KNode { + return lazyList.child { + hasLazyListItemPosition(index) + hasTestTag(OrganizeTokensScreenTestTags.TOKEN_LIST_ITEM) + hasAnyDescendant(withText(tokenTitle)) + useUnmergedTree = true + } + } + + fun tokenDraggableButton(tokenTitle: String): KNode { + return lazyList.child { + hasTestTag(OrganizeTokensScreenTestTags.DRAGGABLE_IMAGE) + useUnmergedTree = true + hasParent(withTestTag(TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK) + .and(withAnySibling(withTestTag(TokenElementsTestTags.TOKEN_TITLE) + .and(withAnyChild(withText(tokenTitle)))) + ) + ) + } + } +} + +internal fun BaseTestCase.onOrganizeTokensScreen(function: OrganizeTokensPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/PopUpMenuPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/PopUpMenuPageObject.kt new file mode 100644 index 0000000000..546da863e9 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/PopUpMenuPageObject.kt @@ -0,0 +1,26 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.R +import com.tangem.core.ui.test.PopUpMenuTestTags +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 PopUpMenuPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val popUpContainer: KNode = child { + hasTestTag(PopUpMenuTestTags.CONTAINER) + } + + val hideTokenButton: KNode = child { + hasTestTag(PopUpMenuTestTags.BUTTON) + hasText(getResourceString(R.string.token_details_hide_token)) + } +} + +internal fun BaseTestCase.onPopUpMenu(function: PopUpMenuPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ 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/TokenDetailsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsPageObject.kt new file mode 100644 index 0000000000..2f8eae1c51 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsPageObject.kt @@ -0,0 +1,19 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.TokenDetailsScreenTestTags +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 TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val screenContainer: KNode = child { + hasTestTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER) + } +} + +internal fun BaseTestCase.onTokenDetailsScreen(function: TokenDetailsPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsTopBarPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsTopBarPageObject.kt new file mode 100644 index 0000000000..9e6f42ef5f --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/TokenDetailsTopBarPageObject.kt @@ -0,0 +1,26 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.TokenDetailsTopBarTestTags +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 TokenDetailsTopBarPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val moreButton: KNode = child { + hasTestTag(TokenDetailsTopBarTestTags.MORE_BUTTON) + useUnmergedTree = true + } + + val backButton: KNode = child { + hasTestTag(TokenDetailsTopBarTestTags.BACK_BUTTON) + useUnmergedTree = true + } + +} + +internal fun BaseTestCase.onTokenDetailsTopBar(function: TokenDetailsTopBarPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ 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/HideTokenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/HideTokenTest.kt new file mode 100644 index 0000000000..dd0dee7e9e --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/HideTokenTest.kt @@ -0,0 +1,58 @@ +package com.tangem.tests + +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.clickWithAssertion +import com.tangem.scenarios.OpenMainScreenScenario +import com.tangem.screens.* +import dagger.hilt.android.testing.HiltAndroidTest +import io.qameta.allure.kotlin.AllureId +import io.qameta.allure.kotlin.junit4.DisplayName +import org.junit.Test + +@HiltAndroidTest +class HideTokenTest : BaseTestCase() { + + @AllureId("880") + @DisplayName("Hide token in 'Token details' screen by 'Hide' button in topBar menu") + @Test + fun hideWalletTokenByHideButtonTest() { + val tokenTitle = "Polygon" + val balance = "<$0.01" + setupHooks().run { + step("Open 'Main Screen'") { + scenario(OpenMainScreenScenario(composeTestRule)) + } + step("Click on 'Synchronize addresses' button" ) { + onMainScreen { synchronizeAddressesButton.clickWithAssertion() } + } + step("Assert wallet balance = $balance") { + onMainScreen { walletBalance().assertTextContains(balance) } + } + step("Click on token with name: '$tokenTitle'") { + onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() } + } + step("Assert 'Token details screen' open") { + onTokenDetailsScreen { screenContainer.assertIsDisplayed() } + } + step("Click 'More button'") { + onTokenDetailsTopBar { moreButton.clickWithAssertion() } + } + step("Click 'Hide token' button") { + onPopUpMenu { + popUpContainer.assertIsDisplayed() + hideTokenButton.clickWithAssertion() + } + } + step("Click 'Hide' button in dialog") { + onDialog { + dialogContainer.assertIsDisplayed() + hideButton.clickWithAssertion() + } + } + step("Assert token: '$tokenTitle' is not displayed") { + onMainScreen { assertTokenDoesNotExist(tokenTitle) } + } + } + } + +} \ No newline at end of file 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/OrganizeTokensTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt new file mode 100644 index 0000000000..3856e30249 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt @@ -0,0 +1,206 @@ +package com.tangem.tests + +import androidx.compose.ui.test.onAllNodesWithText +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.clickWithAssertion +import com.tangem.scenarios.OpenMainScreenScenario +import com.tangem.screens.onMainScreen +import com.tangem.screens.onOrganizeTokensScreen +import dagger.hilt.android.testing.HiltAndroidTest +import io.qameta.allure.kotlin.AllureId +import io.qameta.allure.kotlin.junit4.DisplayName +import org.junit.Test + +@HiltAndroidTest +class OrganizeTokensTest : BaseTestCase() { + + @AllureId("2755") + @DisplayName("Organize tokens: group tokens") + @Test + fun groupTokensTest() { + setupHooks().run { + val tokenTitle = "Ethereum" + val tokenNetwork = "Ethereum network" + step("Open 'Main Screen'") { + scenario(OpenMainScreenScenario(composeTestRule)) + } + step("Click on 'Synchronize addresses' button" ) { + onMainScreen { synchronizeAddressesButton.clickWithAssertion() } + } + step("Click 'Organize tokens' button") { + onMainScreen { organizeTokensButton().clickWithAssertion() } + } + step("Assert 'Organize tokens' screen is opened") { + onOrganizeTokensScreen { + title.assertIsDisplayed() + tokenWithTitle(tokenTitle).assertIsDisplayed() + } + } + step("Click 'Group' button") { + onOrganizeTokensScreen { groupButton.clickWithAssertion() } + } + step("Assert tokens were grouped on 'Organize tokens' screen") { + onOrganizeTokensScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsDisplayed() } + } + step("Click 'Apply' button") { + onOrganizeTokensScreen { applyButton.clickWithAssertion() } + } + step("Assert tokens were grouped on 'Main screen'") { + onMainScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsDisplayed() } + } + step("Click 'Organize tokens' button") { + onMainScreen { organizeTokensButton().clickWithAssertion() } + } + step("Assert 'Organize tokens' screen is opened") { + onOrganizeTokensScreen { + title.assertIsDisplayed() + tokenWithTitle(tokenTitle).assertIsDisplayed() + } + } + step("Click 'Ungroup' button") { + onOrganizeTokensScreen { ungroupButton.clickWithAssertion() } + } + step("Assert tokens were ungrouped on 'Organize tokens' screen") { + onOrganizeTokensScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsNotDisplayed() } + } + step("Click 'Apply' button") { + onOrganizeTokensScreen { applyButton.clickWithAssertion() } + } + step("Assert tokens were ungrouped on 'Main screen'") { + onMainScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsNotDisplayed() } + } + } + } + + @AllureId("2752") + @DisplayName("Organize tokens: check position of tokens") + @Test + fun checkPositionOfTokensTest() { + setupHooks().run { + val ethereumTitle = "Ethereum" + val bitcoinTitle = "Bitcoin" + step("Open 'Main Screen'") { + scenario(OpenMainScreenScenario(composeTestRule)) + } + step("Click on 'Synchronize addresses' button" ) { + onMainScreen { synchronizeAddressesButton.clickWithAssertion() } + } + step("Check positions of tokens on 'Main Screen'") { + onMainScreen { + tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed() + } + } + step("Click 'Organize tokens' button") { + onMainScreen { organizeTokensButton().clickWithAssertion() } + } + step("Check positions of tokens on 'Organize tokens' screen") { + onOrganizeTokensScreen { + tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 2).assertIsDisplayed() + } + } + } + } + + @AllureId("2753") + @DisplayName("Organize tokens: check position of tokens") + // toDo: on test build there is not ability to drag element + // @Test + fun checkCustomTokensOrderTest() { + setupHooks().run { + val ethereumTitle = "Ethereum" + val bitcoinTitle = "Bitcoin" + step("Open 'Main Screen'") { + scenario(OpenMainScreenScenario(composeTestRule)) + } + step("Click on 'Synchronize addresses' button" ) { + onMainScreen { synchronizeAddressesButton.clickWithAssertion() } + } + step("Check positions of tokens on 'Main Screen'") { + onMainScreen { + tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed() + } + } + step("Click 'Organize tokens' button") { + onMainScreen { organizeTokensButton().clickWithAssertion() } + } + step("Drag $bitcoinTitle down on 'Organize tokens' screen") { + composeTestRule.waitUntil(timeoutMillis = 100_000) { + composeTestRule.onAllNodesWithText("Data loaded").fetchSemanticsNodes().isNotEmpty() + } + onOrganizeTokensScreen { + tokenWithTitleAndPosition(bitcoinTitle, 2).assertIsDisplayed() + } + } + step("Click 'Apply' button") { + onOrganizeTokensScreen { applyButton.clickWithAssertion() } + } + step("Check positions of tokens on 'Main Screen'") { + onMainScreen { + tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 0).assertIsDisplayed() + } + } + } + } + + @AllureId("2754") + @DisplayName("Organize tokens: sort by balance") + @Test + fun checkSortByBalanceTest() { + setupHooks().run { + val ethereumTitle = "Ethereum" + val bitcoinTitle = "Bitcoin" + val polygonTitle = "Polygon" + step("Open 'Main Screen'") { + scenario(OpenMainScreenScenario(composeTestRule)) + } + step("Click on 'Synchronize addresses' button" ) { + onMainScreen { synchronizeAddressesButton.clickWithAssertion() } + } + step("Check positions of tokens on 'Main Screen'") { + onMainScreen { + tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed() + } + } + step("Click 'Organize tokens' button") { + onMainScreen { organizeTokensButton().clickWithAssertion() } + } + step("Check positions of tokens on 'Organize tokens' screen") { + onOrganizeTokensScreen { + tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 2).assertIsDisplayed() + tokenWithTitleAndPosition(polygonTitle, 3).assertIsDisplayed() + } + } + step("Click 'By Balance' button") { + onOrganizeTokensScreen { + sortByBalanceButton.clickWithAssertion() + } + } + step("Check positions of tokens by balance on 'Organize tokens' screen") { + onOrganizeTokensScreen { + tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed() + tokenWithTitleAndPosition(bitcoinTitle, 3).assertIsDisplayed() + } + } + step("Click 'Apply' button") { + onOrganizeTokensScreen { applyButton.clickWithAssertion() } + } + step("Check positions of tokens by balance on 'Organize tokens' screen") { + onMainScreen { + tokenWithTitleAndPosition(ethereumTitle, 0).assertIsDisplayed() + tokenWithTitleAndPosition(polygonTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(bitcoinTitle, 2).assertIsDisplayed() + } + } + } + } + + +} \ No newline at end of file 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..776dbb7b61 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -26,17 +26,13 @@ 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.deeplink.DeeplinkConst.WEBLINK_KEY +import com.tangem.common.routing.deeplink.PayloadToDeeplinkConverter import com.tangem.common.routing.entity.SerializableIntent import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.context.AppComponentContext 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 +49,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 +66,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 +99,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 @@ -122,9 +115,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject lateinit var walletConnectInteractor: WalletConnectInteractor - @Inject - lateinit var deepLinksRegistry: DeepLinksRegistry - @Inject lateinit var settingsRepository: SettingsRepository @@ -143,9 +133,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject lateinit var userWalletsListManager: UserWalletsListManager - @Inject - lateinit var emailSender: EmailSender - @Inject @RootAppComponentContext internal lateinit var rootComponentContext: AppComponentContext @@ -174,15 +161,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 +173,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 +228,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 +464,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } } - if (routingFeatureToggle.isDeepLinkNavigationEnabled && intent != null) { + if (intent != null) { handleDeepLink(intent = intent, isFromOnNewIntent = false) } @@ -489,26 +472,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/Analytics.kt b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt index d8dc74443d..f2e7d342ef 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.extensions import com.tangem.core.analytics.Analytics import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor @@ -21,6 +22,15 @@ fun Analytics.setContext(scanResponse: ScanResponse) { addParamsInterceptor(LinkedCardContextInterceptor(scanResponse)) } +fun Analytics.setContext(userWallet: UserWallet) { + setUserId(userWallet.walletId.stringValue) + // TODO add product type for hot ([REDACTED_TASK_KEY]) + + if (userWallet is UserWallet.Cold) { + addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse)) + } +} + /** * Erases the context */ diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index c865434607..5d48278690 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -4,8 +4,8 @@ import com.tangem.common.routing.AppRouter import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.common.extensions.withMainContext +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.redux.StateDialog -import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TapError 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/common/redux/legacy/LegacyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt index a950925df9..15530c62b3 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt @@ -1,8 +1,8 @@ package com.tangem.tap.common.redux.legacy import com.tangem.domain.apptheme.model.AppThemeMode +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.redux.LegacyAction -import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.redux.AppState @@ -37,8 +37,7 @@ internal object LegacyMiddleware { ) store.dispatchWithMain( DetailsAction.PrepareScreen( - // TODO [REDACTED_TASK_KEY] - scanResponse = selectedUserWallet.requireColdWallet().scanResponse, + scanResponse = (selectedUserWallet as? UserWallet.Cold)?.scanResponse, initializedAppSettingsState = initializedAppSettingsStateContent, ), ) @@ -67,7 +66,7 @@ internal object LegacyMiddleware { ?: AppThemeMode.DEFAULT, isHidingEnabled = store.inject(DaggerGraphState::balanceHidingRepository) .getBalanceHidingSettings().isHidingEnabledInSettings, - needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() ?: false, + needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true, ) } } \ 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/data/RuntimeUserWalletsStore.kt b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt index ae906c4a76..dea8dc75a4 100644 --- a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt +++ b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt @@ -2,9 +2,9 @@ package com.tangem.tap.data import com.tangem.common.CompletionResult import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull 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..55d82f0af5 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 @@ -7,8 +7,6 @@ import com.tangem.domain.managetokens.repository.ManageTokensRepository 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.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -71,22 +69,18 @@ internal object ManageTokensDomainModule { walletManagersFacade: WalletManagersFacade, currenciesRepository: CurrenciesRepository, derivationsRepository: DerivationsRepository, - stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, - tokensFeatureToggles: TokensFeatureToggles, ): SaveManagedTokensUseCase { return SaveManagedTokensUseCase( customTokensRepository = customTokensRepository, walletManagersFacade = walletManagersFacade, currenciesRepository = currenciesRepository, derivationsRepository = derivationsRepository, - stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, - tokensFeatureToggles = tokensFeatureToggles, ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt index d23de84aaf..1dab6fc6f5 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -10,8 +10,6 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher -import com.tangem.domain.staking.repositories.StakingRepository -import com.tangem.domain.tokens.TokensFeatureToggles import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import dagger.Module @@ -62,21 +60,17 @@ object MarketsDomainModule { derivationsRepository: DerivationsRepository, marketsTokenRepository: MarketsTokenRepository, currenciesRepository: CurrenciesRepository, - stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, - tokensFeatureToggles: TokensFeatureToggles, ): SaveMarketTokensUseCase { return SaveMarketTokensUseCase( derivationsRepository = derivationsRepository, marketsTokenRepository = marketsTokenRepository, currenciesRepository = currenciesRepository, - stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, - 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/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index c852c1ff14..b6152265bf 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -94,12 +94,10 @@ internal object StakingDomainModule { @Provides @Singleton fun provideFetchStakingYieldBalanceUseCase( - stakingRepository: StakingRepository, stakingErrorResolver: StakingErrorResolver, singleYieldBalanceFetcher: SingleYieldBalanceFetcher, ): FetchStakingYieldBalanceUseCase { return FetchStakingYieldBalanceUseCase( - stakingRepository = stakingRepository, stakingErrorResolver = stakingErrorResolver, singleYieldBalanceFetcher = singleYieldBalanceFetcher, ) diff --git a/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt index f0ba15b856..8cf51ccfe1 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt @@ -3,10 +3,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.swap.SwapErrorResolver import com.tangem.domain.swap.SwapRepositoryV2 import com.tangem.domain.swap.SwapTransactionRepository -import com.tangem.domain.swap.usecase.GetSwapPairsUseCase -import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase -import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase -import com.tangem.domain.swap.usecase.SelectInitialPairUseCase +import com.tangem.domain.swap.usecase.* import com.tangem.feature.swap.domain.GetAvailablePairsUseCase import dagger.Module import dagger.Provides @@ -73,4 +70,30 @@ internal object SwapDomainModule { swapErrorResolver = swapErrorResolver, ) } + + @Provides + @Singleton + fun provideGetSwapDataUseCase( + swapRepositoryV2: SwapRepositoryV2, + swapErrorResolver: SwapErrorResolver, + ): GetSwapDataUseCase { + return GetSwapDataUseCase( + swapRepositoryV2 = swapRepositoryV2, + swapErrorResolver = swapErrorResolver, + ) + } + + @Provides + @Singleton + fun provideSwapTransactionSentUseCase( + swapRepositoryV2: SwapRepositoryV2, + swapTransactionRepository: SwapTransactionRepository, + swapErrorResolver: SwapErrorResolver, + ): SwapTransactionSentUseCase { + return SwapTransactionSentUseCase( + swapRepositoryV2 = swapRepositoryV2, + swapTransactionRepository = swapTransactionRepository, + swapErrorResolver = swapErrorResolver, + ) + } } \ No newline at end of file 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..eddbbc0fd4 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 @@ -40,18 +41,18 @@ internal object TokensDomainModule { @Singleton fun provideAddCryptoCurrenciesUseCase( currenciesRepository: CurrenciesRepository, - stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, tokensFeatureToggles: TokensFeatureToggles, ): AddCryptoCurrenciesUseCase { return AddCryptoCurrenciesUseCase( currenciesRepository = currenciesRepository, - stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles = tokensFeatureToggles, ) } @@ -60,19 +61,15 @@ internal object TokensDomainModule { @Singleton fun provideFetchTokenListUseCase( currenciesRepository: CurrenciesRepository, - stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, - tokensFeatureToggles: TokensFeatureToggles, ): FetchTokenListUseCase { return FetchTokenListUseCase( currenciesRepository = currenciesRepository, - stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, - tokensFeatureToggles = tokensFeatureToggles, ) } @@ -107,8 +104,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 +149,8 @@ internal object TokensDomainModule { currencyChecksRepository: CurrencyChecksRepository, dispatchers: CoroutineDispatcherProvider, baseCurrencyStatusOperations: BaseCurrencyStatusOperations, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): GetCurrencyWarningsUseCase { return GetCurrencyWarningsUseCase( walletManagersFacade = walletManagersFacade, @@ -152,6 +158,8 @@ internal object TokensDomainModule { dispatchers = dispatchers, currencyChecksRepository = currencyChecksRepository, currencyStatusOperations = baseCurrencyStatusOperations, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) } @@ -159,18 +167,18 @@ internal object TokensDomainModule { @Singleton fun provideFetchCurrencyStatusUseCase( currenciesRepository: CurrenciesRepository, - stakingRepository: StakingRepository, singleNetworkStatusFetcher: SingleNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, tokensFeatureToggles: TokensFeatureToggles, ): FetchCurrencyStatusUseCase { return FetchCurrencyStatusUseCase( currenciesRepository = currenciesRepository, - stakingRepository = stakingRepository, singleNetworkStatusFetcher = singleNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles = tokensFeatureToggles, ) } @@ -179,26 +187,26 @@ internal object TokensDomainModule { @Singleton fun provideFetchCardTokenListUseCase( currenciesRepository: CurrenciesRepository, - stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, - tokensFeatureToggles: TokensFeatureToggles, ): FetchCardTokenListUseCase { return FetchCardTokenListUseCase( currenciesRepository = currenciesRepository, - stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, - tokensFeatureToggles = tokensFeatureToggles, ) } @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 +227,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 +295,13 @@ internal object TokensDomainModule { @Singleton fun provideIsCryptoCurrencyCoinCouldHideUseCase( currenciesRepository: CurrenciesRepository, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): IsCryptoCurrencyCoinCouldHideUseCase { return IsCryptoCurrencyCoinCouldHideUseCase( currenciesRepository = currenciesRepository, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) } @@ -300,9 +319,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 +369,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 +404,7 @@ internal object TokensDomainModule { singleQuoteStatusSupplier: SingleQuoteStatusSupplier, singleYieldBalanceSupplier: SingleYieldBalanceSupplier, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, ): BaseCurrenciesStatusesOperations { return CachedCurrenciesStatusesOperations( currenciesRepository = currenciesRepository, @@ -388,6 +419,7 @@ internal object TokensDomainModule { singleYieldBalanceSupplier = singleYieldBalanceSupplier, multiYieldBalanceFetcher = multiYieldBalanceFetcher, tokensFeatureToggles = tokensFeatureToggles, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, ) } @@ -406,6 +438,7 @@ internal object TokensDomainModule { singleQuoteStatusSupplier: SingleQuoteStatusSupplier, singleYieldBalanceSupplier: SingleYieldBalanceSupplier, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, ): BaseCurrencyStatusOperations { return CachedCurrenciesStatusesOperations( currenciesRepository = currenciesRepository, @@ -419,6 +452,7 @@ internal object TokensDomainModule { singleQuoteStatusSupplier = singleQuoteStatusSupplier, singleYieldBalanceSupplier = singleYieldBalanceSupplier, multiYieldBalanceFetcher = multiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles = tokensFeatureToggles, ) } @@ -428,4 +462,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..c426af9672 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,11 +4,14 @@ 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 import com.tangem.domain.transaction.usecase.* import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.tap.domain.hot.TangemHotWalletSigner import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -41,6 +44,7 @@ internal object TransactionDomainModule { transactionRepository: TransactionRepository, walletManagersFacade: WalletManagersFacade, singleNetworkStatusFetcher: SingleNetworkStatusFetcher, + tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory, ): SendTransactionUseCase { return SendTransactionUseCase( demoConfig = DemoConfig(), @@ -48,6 +52,7 @@ internal object TransactionDomainModule { transactionRepository = transactionRepository, walletManagersFacade = walletManagersFacade, singleNetworkStatusFetcher = singleNetworkStatusFetcher, + getHotWalletSigner = tangemHotWalletSignerFactory::create, ) } @@ -58,12 +63,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 f2f9912270..87b107936a 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..f68e642f94 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/hot/TangemHotSdkModule.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.di.hot + +import com.tangem.hot.sdk.TangemHotSdk +import com.tangem.tap.domain.hot.HotWalletPasswordRequester +import com.tangem.tap.features.hot.DefaultHotWalletPasswordRequester +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 + + @Binds + @Singleton + fun bindHotWalletPasswordRequester(impl: DefaultHotWalletPasswordRequester): HotWalletPasswordRequester +} \ 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/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index b812453401..9deccc04e6 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -4,8 +4,7 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.core.analytics.Analytics import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.requireColdWallet +import com.tangem.domain.models.wallet.UserWallet import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.store @@ -35,14 +34,15 @@ class TapWalletManager( } private suspend fun loadUserWalletData(userWallet: UserWallet) { - Analytics.setContext(userWallet.requireColdWallet().scanResponse) // [REDACTED_TASK_KEY] - val scanResponse = userWallet.scanResponse + Analytics.setContext(userWallet) - tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) - - withMainContext { - // Order is important - store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) + if (userWallet is UserWallet.Cold) { + val scanResponse = userWallet.scanResponse + tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) + withMainContext { + // Order is important + store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) + } } } } diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index 4c6e048c73..b09a27d4cd 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -17,9 +17,9 @@ import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.domain.wallets.models.requireColdWallet +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter @@ -50,7 +50,7 @@ internal class DefaultDerivationsRepository( networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, - scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] + userWallet = userWallet, ) }, ) @@ -61,7 +61,11 @@ internal class DefaultDerivationsRepository( userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") } - userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY] + if (userWallet is UserWallet.Hot) { + return + } + + userWallet.requireColdWallet() if (!userWallet.scanResponse.card.settings.isHDWalletAllowed) { Timber.d("Nothing to derive") @@ -84,14 +88,18 @@ internal class DefaultDerivationsRepository( ): Boolean { val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") + if (userWallet is UserWallet.Hot) { + return false + } + val derivations = - MissedDerivationsFinder(scanResponse = userWallet.requireColdWallet().scanResponse) // TODO [REDACTED_TASK_KEY] + MissedDerivationsFinder(scanResponse = userWallet.requireColdWallet().scanResponse) .findByNetworks( networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) -> networkFactory.create( blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null, extraDerivationPath = extraDerivationPath, - scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] + userWallet = userWallet, ) }, ) diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt index aec5e21d16..b86f57ce33 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt @@ -13,7 +13,7 @@ import com.tangem.common.core.UserCodeRequestPolicy import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.card.ResetCardUserCodeParams import com.tangem.domain.card.models.ResetCardError -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.sdk.api.TangemSdkManager internal class DefaultResetCardUseCase( 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/hot/HotWalletAccessor.kt b/app/src/main/java/com/tangem/tap/domain/hot/HotWalletAccessor.kt new file mode 100644 index 0000000000..a65a2ecd53 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/hot/HotWalletAccessor.kt @@ -0,0 +1,113 @@ +package com.tangem.tap.domain.hot + +import com.tangem.common.core.TangemSdkError +import com.tangem.features.hotwallet.HotWalletPasswordRequester +import com.tangem.hot.sdk.TangemHotSdk +import com.tangem.hot.sdk.exception.WrongPasswordException +import com.tangem.hot.sdk.model.* +import javax.inject.Inject + +class HotWalletAccessor @Inject constructor( + private val tangemHotSdk: TangemHotSdk, + private val hotWalletPasswordRequester: HotWalletPasswordRequester, +) { + + suspend fun signHashes(hotWalletId: HotWalletId, dataToSign: List): List { + val auth = when (hotWalletId.authType) { + HotWalletId.AuthType.NoPassword -> HotAuth.NoAuth + HotWalletId.AuthType.Password -> requestPassword(false) + HotWalletId.AuthType.Biometry -> HotAuth.Biometry + } + + return runCatchingSdkErrors(hotWalletId, auth) { + tangemHotSdk.signHashes( + unlockHotWallet = UnlockHotWallet( + walletId = hotWalletId, + auth = it, + ), + dataToSign = dataToSign, + ).also { + hotWalletPasswordRequester.dismiss() + } + } + } + + private suspend fun runCatchingSdkErrors( + hotWalletId: HotWalletId, + auth: HotAuth, + block: suspend (auth: HotAuth) -> T, + ): T { + return runCatchingWrongPassInternal( + originalAuth = auth, + auth = auth, + block = { blockAuth -> + block(blockAuth).also { + // TODO [REDACTED_TASK_KEY] if user has biometry enabled, we set it as the new auth method + if (blockAuth is HotAuth.Password /*&& has biometry enabled */) { + tangemHotSdk.changeAuth( + unlockHotWallet = UnlockHotWallet( + walletId = hotWalletId, + auth = blockAuth, + ), + auth = HotAuth.Biometry, + ) + } + } + }, + ) + } + + private suspend fun runCatchingWrongPassInternal( + originalAuth: HotAuth, + auth: HotAuth, + block: suspend (auth: HotAuth) -> T, + ): T = runCatching { + block(auth) + }.getOrElse { exception -> + if (auth is HotAuth.Biometry && exception.isBiometryError()) { + // fallback to password if biometry fails + val passAuth = requestPassword(true) + + return@getOrElse runCatchingWrongPassInternal( + originalAuth = originalAuth, + auth = passAuth, + block = block, + ) + } + + if (exception !is WrongPasswordException) { + throw exception + } + + // If the exception is a wrong password, we need to request the password again + + hotWalletPasswordRequester.wrongPassword() + val passResult = requestPassword(originalAuth is HotAuth.Biometry) + + runCatchingWrongPassInternal( + originalAuth = originalAuth, + auth = passResult, + block = block, + ) + } + + private suspend fun requestPassword(hasBiometry: Boolean): HotAuth { + return hotWalletPasswordRequester.requestPassword(hasBiometry).toAuth() ?: throw TangemSdkError.UserCancelled() + } + + private fun Throwable.isBiometryError(): Boolean { + return this is TangemSdkError.AuthenticationFailed || + this is TangemSdkError.AuthenticationCanceled || + this is TangemSdkError.AuthenticationLockout || + this is TangemSdkError.AuthenticationUnavailable || + this is TangemSdkError.AuthenticationAlreadyInProgress || + this is TangemSdkError.AuthenticationNotInitialized || + this is TangemSdkError.AuthenticationPermanentLockout + } + + private fun HotWalletPasswordRequester.Result.toAuth() = when (this) { + HotWalletPasswordRequester.Result.UseBiometry -> HotAuth.Biometry + HotWalletPasswordRequester.Result.Dismiss -> null + is HotWalletPasswordRequester.Result.EnteredPassword -> this.password + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/hot/HotWalletPasswordRequester.kt b/app/src/main/java/com/tangem/tap/domain/hot/HotWalletPasswordRequester.kt new file mode 100644 index 0000000000..1f80fd785f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/hot/HotWalletPasswordRequester.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.domain.hot + +import com.tangem.hot.sdk.model.HotAuth +import com.tangem.hot.sdk.model.HotWalletId + +interface HotWalletPasswordRequester { + + suspend fun requestPassword(hotWalletId: HotWalletId): HotAuth.Password +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/hot/TangemHotSigner.kt b/app/src/main/java/com/tangem/tap/domain/hot/TangemHotSigner.kt new file mode 100644 index 0000000000..0b71ca974c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/hot/TangemHotSigner.kt @@ -0,0 +1,78 @@ +package com.tangem.tap.domain.hot + +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.blockchain.common.Wallet +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError +import com.tangem.common.map +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.hot.sdk.model.DataToSign +import com.tangem.operations.sign.SignData +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +class TangemHotSigner @AssistedInject constructor( + @Assisted private val userWallet: UserWallet.Hot, + private val hotWalletAccessor: HotWalletAccessor, +) : TransactionSigner { + + override suspend fun sign(hash: ByteArray, publicKey: Wallet.PublicKey): CompletionResult { + return sign(listOf(hash), publicKey).map { it.first() } + } + + override suspend fun sign( + hashes: List, + publicKey: Wallet.PublicKey, + ): CompletionResult> { + val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey == publicKey.seedKey } + ?: return CompletionResult.Failure( + TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")), + ) + + val result = hotWalletAccessor.signHashes( + hotWalletId = userWallet.hotWalletId, + dataToSign = listOf( + DataToSign( + curve = wallet.curve, + hashes = hashes, + derivationPath = publicKey.derivationPath, + ), + ), + ) + + return CompletionResult.Success(result.map { it.signatures }.flatten()) + } + + override suspend fun multiSign( + dataToSign: List, + publicKey: Wallet.PublicKey, + ): CompletionResult> { + val result = hotWalletAccessor.signHashes( + hotWalletId = userWallet.hotWalletId, + dataToSign = dataToSign.map { signData -> + val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey == signData.publicKey } + ?: return CompletionResult.Failure( + TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")), + ) + + DataToSign( + curve = wallet.curve, + hashes = listOf(signData.hash), + derivationPath = signData.derivationPath, + ) + }, + ) + + return CompletionResult.Success( + result.mapIndexed { index, data -> + dataToSign[index].publicKey to data.signatures.first() + }.toMap(), + ) + } + + @AssistedFactory + interface Factory { + fun create(@Assisted userWallet: UserWallet.Hot): TangemHotSigner + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/hot/TangemHotWalletSigner.kt b/app/src/main/java/com/tangem/tap/domain/hot/TangemHotWalletSigner.kt new file mode 100644 index 0000000000..ba64c2fda2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/hot/TangemHotWalletSigner.kt @@ -0,0 +1,98 @@ +package com.tangem.tap.domain.hot + +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.blockchain.common.Wallet +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError +import com.tangem.common.map +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.hot.sdk.model.DataToSign +import com.tangem.operations.sign.SignData +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import timber.log.Timber + +class TangemHotWalletSigner @AssistedInject constructor( + @Assisted private val userWallet: UserWallet.Hot, + private val hotWalletAccessor: HotWalletAccessor, +) : TransactionSigner { + + override suspend fun sign(hash: ByteArray, publicKey: Wallet.PublicKey): CompletionResult { + return sign(listOf(hash), publicKey).map { it.first() } + } + + override suspend fun sign( + hashes: List, + publicKey: Wallet.PublicKey, + ): CompletionResult> { + val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey.contentEquals(publicKey.seedKey) } + ?: return CompletionResult.Failure( + TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")), + ) + + val result = runCatching { + hotWalletAccessor.signHashes( + hotWalletId = userWallet.hotWalletId, + dataToSign = listOf( + DataToSign( + curve = wallet.curve, + hashes = hashes, + derivationPath = publicKey.derivationPath, + ), + ), + ) + }.getOrElse { + Timber.e(it) + return if (it is TangemSdkError) { + CompletionResult.Failure(it) + } else { + CompletionResult.Failure(TangemSdkError.ExceptionError(it)) + } + } + + return CompletionResult.Success(result.map { it.signatures }.flatten()) + } + + override suspend fun multiSign( + dataToSign: List, + publicKey: Wallet.PublicKey, + ): CompletionResult> { + val result = runCatching { + hotWalletAccessor.signHashes( + hotWalletId = userWallet.hotWalletId, + dataToSign = dataToSign.map { signData -> + val wallet = + userWallet.wallets.orEmpty().firstOrNull { it.publicKey.contentEquals(signData.publicKey) } + ?: return CompletionResult.Failure( + TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")), + ) + + DataToSign( + curve = wallet.curve, + hashes = listOf(signData.hash), + derivationPath = signData.derivationPath, + ) + }, + ) + }.getOrElse { + Timber.e(it) + return if (it is TangemSdkError) { + CompletionResult.Failure(it) + } else { + CompletionResult.Failure(TangemSdkError.ExceptionError(it)) + } + } + + return CompletionResult.Success( + result.mapIndexed { index, data -> + dataToSign[index].publicKey to data.signatures.first() + }.toMap(), + ) + } + + @AssistedFactory + interface Factory { + fun create(@Assisted userWallet: UserWallet.Hot): TangemHotWalletSigner + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index 7ef7432c75..b9ed35b393 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -23,10 +23,10 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.visa.model.VisaActivationInput import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.operations.ScanTask import com.tangem.operations.derivation.DerivationTaskResponse diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index bf29d6cfd3..edca4bea1a 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -17,10 +17,10 @@ import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.visa.model.VisaActivationInput import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.preflightread.PreflightReadFilter import com.tangem.operations.wallet.CreateWalletResponse diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt index 8b3f45673e..f562753a55 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt @@ -177,6 +177,13 @@ object WalletMockContent : MockContent { parentFingerprint = byteArrayOf(0, 0, 0, 0), childNumber = 0, ), + DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey( // btc + publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52), + chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70), + depth = 0, + parentFingerprint = byteArrayOf(0, 0, 0, 0), + childNumber = 0, + ), DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67), chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109), diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/UserWalletIdPreflightReadFilter.kt b/app/src/main/java/com/tangem/tap/domain/tasks/UserWalletIdPreflightReadFilter.kt index d0f1c97dda..13a0008984 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/UserWalletIdPreflightReadFilter.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/UserWalletIdPreflightReadFilter.kt @@ -4,8 +4,8 @@ import com.tangem.common.card.Card import com.tangem.common.core.SessionEnvironment import com.tangem.common.core.TangemSdkError import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.builder.UserWalletIdBuilder -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.preflightread.PreflightReadFilter /** diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt index 6fde516a55..9f45ceacff 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/DerivationsFinder.kt @@ -9,8 +9,8 @@ import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.builder.UserWalletIdBuilder -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.features.demo.DemoHelper import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetBackupCardTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetBackupCardTask.kt index 096aa23b39..dd90db55fa 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetBackupCardTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ResetBackupCardTask.kt @@ -4,7 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt index 1612d89f64..49ac95c749 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt @@ -7,9 +7,6 @@ internal class DefaultTokensFeatureToggles( private val featureTogglesManager: FeatureTogglesManager, ) : TokensFeatureToggles { - override val isStakingLoadingRefactoringEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_LOADING_REFACTORING_ENABLED") - override val isWalletBalanceFetcherEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled(name = "WALLET_BALANCE_FETCHER_ENABLED") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index bd05d22cf7..956ce71605 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -1,13 +1,12 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.* +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.wallets.legacy.UserWalletsListError import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.domain.wallets.models.isLocked -import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository @@ -213,8 +212,7 @@ internal class BiometricUserWalletsListManager( changeSelectedUserWallet: Boolean, canOverridePublicInfo: Boolean, ): CompletionResult { - userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY] - val encryptionKey = userWallet.scanResponse.card.encryptionKey + val encryptionKey = userWallet.encryptionKey ?.let { UserWalletEncryptionKey(userWallet.walletId, it) } ?: return CompletionResult.Success(Unit) // No encryption key, no need to save diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 1be31c95b6..4ae134f570 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -4,9 +4,9 @@ import com.tangem.common.CompletionResult import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.get +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index 43bd7646e0..900c4bb7bf 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -2,10 +2,10 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.CompletionResult import com.tangem.common.catching +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.legacy.UserWalletsListError import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt index 0a1b637e5b..485ea94aae 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.userWalletList.model import com.squareup.moshi.JsonClass -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId @JsonClass(generateAdapter = true) internal data class UserWalletEncryptionKey( diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt index 95e75741b7..b4019602d9 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt @@ -2,31 +2,44 @@ package com.tangem.tap.domain.userWalletList.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import com.tangem.domain.models.MobileWallet import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.visa.model.VisaCardActivationStatus -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.hot.sdk.model.HotWalletId @JsonClass(generateAdapter = true) internal data class UserWalletSensitiveInformation( + // Cold @Json(name = "wallets") - val wallets: List, + val wallets: List?, @Json(name = "visaCardActivationStatus") val visaCardActivationStatus: VisaCardActivationStatus? = null, + // Hot + @Json(name = "mobileWallets") + val mobileWallets: List? = null, ) @JsonClass(generateAdapter = true) internal data class UserWalletPublicInformation( + // Common @Json(name = "name") val name: String, @Json(name = "walletId") val walletId: UserWalletId, + // Cold @Json(name = "cardsInWallet") val cardsInWallet: Set, @Json(name = "scanResponse") - val scanResponse: ScanResponse, + val scanResponse: ScanResponse?, @Json(name = "isMultiCurrency") val isMultiCurrency: Boolean, @Json(name = "hasBackupError") val hasBackupError: Boolean = false, + // Hot + @Json(name = "hotWalletId") + val hotWalletId: HotWalletId? = null, + @Json(name = "backedUp") + val backedUp: Boolean? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/SelectedUserWalletRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/SelectedUserWalletRepository.kt index 5aab49f486..c9f84cfcea 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/SelectedUserWalletRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/SelectedUserWalletRepository.kt @@ -1,6 +1,6 @@ package com.tangem.tap.domain.userWalletList.repository -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId internal interface SelectedUserWalletRepository { suspend fun get(): UserWalletId? diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index 170296fbe1..0c196308db 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.userWalletList.repository import com.tangem.common.CompletionResult -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey internal interface UserWalletsKeysRepository { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt index 383807f8fa..6daa482e36 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt @@ -1,8 +1,8 @@ package com.tangem.tap.domain.userWalletList.repository import com.tangem.common.CompletionResult -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation internal interface UserWalletsPublicInformationRepository { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt index 15d9abbee4..31ab0224b5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt @@ -1,8 +1,8 @@ package com.tangem.tap.domain.userWalletList.repository import com.tangem.common.CompletionResult -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 96b486a33f..1a3d1c3b38 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -10,8 +10,8 @@ import com.tangem.common.services.secure.SecureStorage import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.legacy.UserWalletsListError -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt index 7a17c85cd9..8398b7726d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.userWalletList.repository.implementation import com.tangem.common.services.secure.SecureStorage -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index d963dc5794..a66ba5bdbb 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -7,8 +7,8 @@ import com.tangem.common.CompletionResult import com.tangem.common.catching import com.tangem.common.flatMap import com.tangem.common.services.secure.SecureStorage -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.utils.publicInformation diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index a1888ea494..23d1e3e069 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -7,8 +7,8 @@ import com.tangem.common.CompletionResult import com.tangem.common.catching import com.tangem.common.services.secure.SecureStorage import com.tangem.crypto.operations.AESCipherOperations -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt index df0951fab2..ec627a4e34 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -1,7 +1,8 @@ package com.tangem.tap.domain.userWalletList.utils -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation @@ -10,8 +11,12 @@ internal val UserWallet.sensitiveInformation: UserWalletSensitiveInformation is UserWallet.Cold -> UserWalletSensitiveInformation( wallets = scanResponse.card.wallets, visaCardActivationStatus = scanResponse.visaCardActivationStatus, + mobileWallets = null, + ) + is UserWallet.Hot -> UserWalletSensitiveInformation( + wallets = null, + mobileWallets = this.wallets, ) - is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]") } internal val UserWallet.publicInformation: UserWalletPublicInformation @@ -28,19 +33,39 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation visaCardActivationStatus = null, ), hasBackupError = hasBackupError, + hotWalletId = null, + backedUp = null, + ) + is UserWallet.Hot -> UserWalletPublicInformation( + name = name, + walletId = walletId, + isMultiCurrency = isMultiCurrency, + cardsInWallet = emptySet(), + scanResponse = null, + hotWalletId = hotWalletId, + backedUp = backedUp, ) - is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]") } internal fun UserWalletPublicInformation.toUserWallet(): UserWallet { - return UserWallet.Cold( - name = name, - walletId = walletId, - cardsInWallet = cardsInWallet, - scanResponse = scanResponse, - isMultiCurrency = isMultiCurrency, - hasBackupError = hasBackupError, - ) + return if (hotWalletId != null) { + UserWallet.Hot( + name = name, + walletId = walletId, + hotWalletId = hotWalletId, + wallets = null, + backedUp = backedUp!!, + ) + } else { + UserWallet.Cold( + name = name, + walletId = walletId, + cardsInWallet = cardsInWallet, + scanResponse = scanResponse!!, + isMultiCurrency = isMultiCurrency, + hasBackupError = hasBackupError, + ) + } } internal fun List.toUserWallets(): List { @@ -53,13 +78,15 @@ internal fun UserWallet.updateWith(sensitiveInformation: UserWalletSensitiveInfo copy( scanResponse = scanResponse.copy( card = scanResponse.card.copy( - wallets = sensitiveInformation.wallets, + wallets = sensitiveInformation.wallets!!, ), visaCardActivationStatus = sensitiveInformation.visaCardActivationStatus, ), ) } - is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]") + is UserWallet.Hot -> copy( + wallets = sensitiveInformation.mobileWallets, + ) } } @@ -90,5 +117,5 @@ internal fun UserWallet.lock(): UserWallet = when (this) { ), ) } - is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]") + is UserWallet.Hot -> copy(wallets = null) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt index f58758f35a..8b05c741e7 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt @@ -1,11 +1,16 @@ package com.tangem.tap.domain.userWalletList.utils import com.tangem.common.extensions.calculateSha256 -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.common.extensions.calculateHmacSha256 +import com.tangem.domain.models.MobileWallet +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.wallet.UserWallet -internal val CardDTO.encryptionKey: ByteArray? - get() = findPublicKey(wallets)?.let { calculateEncryptionKey(it) } +internal val UserWallet.encryptionKey: ByteArray? + get() = when (this) { + is UserWallet.Cold -> findPublicKey(this.scanResponse.card.wallets) + is UserWallet.Hot -> findPublicKey(this.wallets.orEmpty()) + }?.let { calculateEncryptionKey(it) } private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() @@ -15,8 +20,12 @@ private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { } private fun findPublicKey(wallets: List): ByteArray? { - return wallets.firstOrNull() - ?.publicKey + return wallets.firstOrNull()?.publicKey +} + +@JvmName("findPublicKeyInMobileWallets") +private fun findPublicKey(wallets: List): ByteArray? { + return wallets.firstOrNull()?.publicKey } private const val MESSAGE_FOR_ENCRYPTION_KEY = "UserWalletEncryptionKey" \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index 02c60262fc..ffb1dbb2b0 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -17,7 +17,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper -import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.models.wallet.UserWallet import com.tangem.operations.sign.SignHashCommand import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.safeUpdate 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..922bb0e107 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,12 +8,12 @@ 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.models.wallet.UserWalletId import com.tangem.domain.walletconnect.WcPairService import com.tangem.domain.walletconnect.model.WcPairRequest import com.tangem.domain.walletconnect.model.legacy.Account import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper @@ -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/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt index c0e22a3b7d..55b4899aa9 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/LegacyWalletConnectRepository.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.walletconnect2.domain +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.walletconnect.model.legacy.Account -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.domain.walletconnect2.domain.models.* import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 069e6089e9..f602ed8fc4 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -5,16 +5,15 @@ import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletconnect.model.legacy.Account import com.tangem.domain.walletconnect.model.legacy.Session import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.domain.wallets.models.isMultiCurrency -import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.tap.common.extensions.dispatchOnMain @@ -422,8 +421,7 @@ class WalletConnectInteractor( } private fun getCardId(userWallet: UserWallet): String? { - userWallet.requireColdWallet() // [REDACTED_TASK_KEY] - return if (userWallet.scanResponse.card.backupStatus?.isActive != true) { + return if (userWallet is UserWallet.Cold && userWallet.scanResponse.card.backupStatus?.isActive != true) { userWallet.cardId } else { // if wallet has backup, any card from wallet can be used to sign null 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/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 56b822cc8e..d398da42c9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -9,7 +9,7 @@ import org.rekotlin.Action sealed class DetailsAction : Action { data class PrepareScreen( - val scanResponse: ScanResponse, + val scanResponse: ScanResponse?, val initializedAppSettingsState: AppSettingsState, ) : DetailsAction() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 4288f711b9..bc90c602d0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.details.redux.walletconnect -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/api/CardSettingsComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/api/CardSettingsComponent.kt index 608362caa8..b8dc35ed30 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/api/CardSettingsComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/api/CardSettingsComponent.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.cardsettings.api import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId interface CardSettingsComponent : ComposableContentComponent { 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..49d7e42d4c 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 @@ -17,9 +17,9 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.getBackupCardsCount import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.builder.UserWalletIdBuilder -import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -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/resetcard/api/ResetCardComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/api/ResetCardComponent.kt index abbe76595b..64dc8b1c5d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/api/ResetCardComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/api/ResetCardComponent.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.resetcard.api import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId interface ResetCardComponent : ComposableContentComponent { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt index 34aa6d687a..f0b4bd8e02 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt @@ -12,9 +12,9 @@ import com.tangem.domain.card.DeleteSavedAccessCodesUseCase import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.card.ResetCardUserCodeParams import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable -import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/api/SecurityModeComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/api/SecurityModeComponent.kt index a09d7ba6c9..a95e261496 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/api/SecurityModeComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/api/SecurityModeComponent.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.securitymode.api import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId interface SecurityModeComponent : ComposableContentComponent { 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/details/ui/walletconnect/api/WalletConnectComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt index 6b9d8c4330..2c97e35722 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/api/WalletConnectComponent.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.walletconnect.api import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId interface WalletConnectComponent : ComposableContentComponent { data class Params(val userWalletId: UserWalletId) diff --git a/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt b/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt index ade00b0024..b89b6f5255 100644 --- a/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt @@ -13,6 +13,7 @@ import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.components.SystemBarsIconsDisposable import com.tangem.core.ui.utils.ChangeRootBackgroundColorEffect import com.tangem.core.ui.utils.findActivity +import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.home.api.HomeComponent import com.tangem.tap.features.home.compose.StoriesScreen @@ -29,6 +30,7 @@ import org.rekotlin.StoreSubscriber internal class DefaultHomeComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted params: Unit, + private val hotWalletFeatureToggles: HotWalletFeatureToggles, ) : HomeComponent, AppComponentContext by appComponentContext, StoreSubscriber { private val model: HomeModel = getOrCreateModel() @@ -58,7 +60,7 @@ internal class DefaultHomeComponent @AssistedInject constructor( val activity = LocalContext.current.findActivity() BackHandler(onBack = activity::finish) SystemBarsIconsDisposable(darkIcons = false) - if (homeState.value.isV2StoriesEnabled) { + if (hotWalletFeatureToggles.isHotWalletEnabled) { StoriesScreenV2( homeState = homeState, onCreateNewWalletButtonClick = model::onCreateNewWalletScreen, 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/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt index d36ea27e67..b1d45779ba 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt @@ -7,7 +7,6 @@ import org.rekotlin.StateType // todo refactor [REDACTED_TASK_KEY] data class HomeState( val scanInProgress: Boolean = false, - val isV2StoriesEnabled: Boolean = false, val stories: ImmutableList = getRestrictedStories().toImmutableList(), ) : StateType { diff --git a/app/src/main/java/com/tangem/tap/features/hot/DefaultHotWalletPasswordRequester.kt b/app/src/main/java/com/tangem/tap/features/hot/DefaultHotWalletPasswordRequester.kt new file mode 100644 index 0000000000..b9d1c6ae6c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/hot/DefaultHotWalletPasswordRequester.kt @@ -0,0 +1,13 @@ +package com.tangem.tap.features.hot + +import com.tangem.hot.sdk.model.HotAuth +import com.tangem.hot.sdk.model.HotWalletId +import com.tangem.tap.domain.hot.HotWalletPasswordRequester +import javax.inject.Inject + +class DefaultHotWalletPasswordRequester @Inject constructor() : HotWalletPasswordRequester { + + override suspend fun requestPassword(hotWalletId: HotWalletId): HotAuth.Password { + return HotAuth.Password("TODO [REDACTED_TASK_KEY]".toCharArray()) // TODO [REDACTED_TASK_KEY] + } +} \ No newline at end of file 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 d3a65f5ace..6b5c655b9e 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,16 @@ 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.models.wallet.requireColdWallet import com.tangem.domain.notifications.GetApplicationIdUseCase import com.tangem.domain.notifications.SendPushTokenUseCase import com.tangem.domain.notifications.models.ApplicationId @@ -37,15 +40,16 @@ 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.requireColdWallet import com.tangem.domain.wallets.usecase.AssociateWalletsWithApplicationIdUseCase import com.tangem.domain.wallets.usecase.GetSavedWalletsCountUseCase 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 +62,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 +82,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: GetSavedWalletsCountUseCase, @@ -88,7 +90,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 +123,8 @@ internal class MainViewModel @Inject constructor( multiQuoteUpdater.subscribe() + initializeOffRamp() + observeFlips() displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() @@ -129,10 +134,6 @@ internal class MainViewModel @Inject constructor( sendKeyboardIdentifierEvent() preloadImages() - - if (!routingFeatureToggle.isDeepLinkNavigationEnabled) { - initializeDeepLinks() - } } override fun onCleared() { @@ -189,7 +190,7 @@ internal class MainViewModel @Inject constructor( userWalletsListManager.selectedUserWallet .distinctUntilChanged() .onEach { userWallet -> - Analytics.setContext(userWallet.requireColdWallet().scanResponse) // TODO [REDACTED_TASK_KEY] + Analytics.setContext(userWallet) } .flowOn(dispatchers.io) .launchIn(viewModelScope) @@ -201,6 +202,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 +407,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 +416,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/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index bf97ad0ae2..db39ad8c56 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -1,7 +1,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWalletId import org.rekotlin.Action sealed class OnboardingWalletAction : Action { 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/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index ef31519427..71e1887062 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -13,20 +13,16 @@ import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.legacy.unlockIfLockable -import com.tangem.domain.wallets.models.requireColdWallet -import com.tangem.tap.backupService +import com.tangem.tap.* import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler -import com.tangem.tap.mainScope import com.tangem.tap.proxy.redux.DaggerGraphState -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -95,7 +91,7 @@ internal class WelcomeMiddleware { } .doOnSuccess { selectedUserWallet -> sendSignedInAnalyticsEvent( - scanResponse = selectedUserWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] + userWallet = selectedUserWallet, signInType = Basic.SignedIn.SignInType.Biometric, ) @@ -129,7 +125,7 @@ internal class WelcomeMiddleware { store.dispatchWithMain(WelcomeAction.ProceedWithCard.Error(error)) } .doOnSuccess { - sendSignedInAnalyticsEvent(scanResponse = scanResponse, signInType = Basic.SignedIn.SignInType.Card) + sendSignedInAnalyticsEvent(userWallet, signInType = Basic.SignedIn.SignInType.Card) store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) } store.dispatchWithMain(WelcomeAction.ProceedWithCard.Success) @@ -142,7 +138,14 @@ internal class WelcomeMiddleware { } } - private fun sendSignedInAnalyticsEvent(scanResponse: ScanResponse, signInType: Basic.SignedIn.SignInType) { + private fun sendSignedInAnalyticsEvent(userWallet: UserWallet, signInType: Basic.SignedIn.SignInType) { + // TODO [REDACTED_TASK_KEY] + + if (userWallet !is UserWallet.Cold) { + return + } + + val scanResponse = userWallet.scanResponse val currency = ParamCardCurrencyConverter().convert( value = scanResponse.cardTypesResolver, ) 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..5ce4ad3c20 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 @@ -2,20 +2,29 @@ package com.tangem.tap.network.auth import com.tangem.common.extensions.toHexString import com.tangem.datasource.api.common.AuthProvider +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.models.UserWallet -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 + + if (userWallet !is UserWallet.Cold) { + return "" + } + + return userWallet.scanResponse.card.cardPublicKey.toHexString() } override fun getCardId(): String { - return userWalletsListManager.selectedUserWalletSync - ?.requireColdWallet()?.scanResponse?.card?.cardId ?: "" + val userWallet = userWalletsListManager.selectedUserWalletSync + + if (userWallet !is UserWallet.Cold) { + return "" + } + + return userWallet.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/CryptoCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt index e69337350b..f71c4160f9 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CryptoCurrencyConverter.kt @@ -6,7 +6,6 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.tap.common.extensions.inject import com.tangem.tap.domain.model.Currency import com.tangem.tap.proxy.redux.DaggerGraphState @@ -25,10 +24,8 @@ internal class CryptoCurrencyConverter( cryptoCurrencyFactory.createCoin( blockchain = value.blockchain, extraDerivationPath = value.derivationPath, - scanResponse = requireNotNull( - store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync - ?.requireColdWallet() // TODO [REDACTED_TASK_KEY] - ?.scanResponse, + userWallet = requireNotNull( + store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync, ), ), ) @@ -37,10 +34,8 @@ internal class CryptoCurrencyConverter( sdkToken = value.token, blockchain = value.blockchain, extraDerivationPath = value.derivationPath, - scanResponse = requireNotNull( - store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync - ?.requireColdWallet() // TODO [REDACTED_TASK_KEY] - ?.scanResponse, + userWallet = requireNotNull( + store.inject(DaggerGraphState::generalUserWalletsListManager).selectedUserWalletSync, ), ), ) 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..be5a5a9705 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 @@ -13,12 +13,11 @@ import com.tangem.domain.core.lce.Lce import com.tangem.domain.exchange.ExpressAvailabilityState import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId 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,26 +27,22 @@ 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 { private val cryptoCurrencyConverter = CryptoCurrencyConverter(excludedBlockchains) override suspend fun availableForBuy( - scanResponse: ScanResponse, - userWalletId: UserWalletId, + userWallet: UserWallet, cryptoCurrency: CryptoCurrency, ): ScenarioUnavailabilityReason { - val availabilityState = runCatching { - getOnrampAvailableState(scanResponse, userWalletId, cryptoCurrency) - }.getOrNull() ?: ExpressAvailabilityState.Error + val availabilityState = runCatching { getOnrampAvailableState(userWallet.walletId, cryptoCurrency) } + .getOrNull() + ?: ExpressAvailabilityState.Error return availabilityState.toReason(cryptoCurrency.name) } @@ -62,7 +57,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 +95,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 +148,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..cee10eaf8f 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -1,8 +1,8 @@ package com.tangem.tap.proxy +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.redux.StateDialog -import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.onUserWalletSelected @@ -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/RootContent.kt b/app/src/main/java/com/tangem/tap/routing/RootContent.kt index f312690af5..5c009849bc 100644 --- a/app/src/main/java/com/tangem/tap/routing/RootContent.kt +++ b/app/src/main/java/com/tangem/tap/routing/RootContent.kt @@ -34,6 +34,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.routing.component.RoutingComponent import com.tangem.tap.routing.transitions.RoutingTransitionAnimationFactory +@Suppress("LongParameterList") @OptIn(ExperimentalDecomposeApi::class) @Composable internal fun RootContent( @@ -41,6 +42,7 @@ internal fun RootContent( backHandler: BackHandler, uiDependencies: UiDependencies, wcContent: @Composable (modifier: Modifier) -> Unit, + hotAccessCodeContent: @Composable (modifier: Modifier) -> Unit, onBack: () -> Unit, modifier: Modifier = Modifier, ) { @@ -74,6 +76,8 @@ internal fun RootContent( wcContent(Modifier.fillMaxSize()) + hotAccessCodeContent(Modifier.fillMaxSize()) + TangemSnackbarHost( modifier = Modifier .align(Alignment.BottomCenter) 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..86b7acd8d4 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,17 +6,24 @@ 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 +import com.tangem.core.decompose.context.child import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.navigation.getOrCreateTyped import com.tangem.core.ui.UiDependencies 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.hotwallet.HotAccessCodeRequestComponent +import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy 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,13 +43,21 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val uiDependencies: UiDependencies, private val wcRoutingComponentFactory: WcRoutingComponent.Factory, private val deeplinkFactory: DeepLinkFactory, + private val tangemHotSDKProxy: TangemHotSDKProxy, + private val hotAccessCodeRequestComponentFactory: HotAccessCodeRequestComponent.Factory, + private val hotAccessCodeRequesterProxy: HotWalletPasswordRequesterProxy, ) : RoutingComponent, AppComponentContext by context, SnackbarHandler { private val wcRoutingComponent: WcRoutingComponent by lazy { wcRoutingComponentFactory - .create(childByContext(componentContext = this), params = Unit) + .create(child("wcRoutingComponent"), params = Unit) + } + + private val hotAccessCodeRequestComponent: HotAccessCodeRequestComponent by lazy { + hotAccessCodeRequestComponentFactory + .create(child("hotAccessCodeRequestComponent"), Unit) } private val stack: Value> = childStack( @@ -70,6 +85,8 @@ internal class DefaultRoutingComponent @AssistedInject constructor( appRouterConfig.stack = stackItems } } + + configureProxies() } @Composable @@ -79,6 +96,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( stack = stack, uiDependencies = uiDependencies, wcContent = { wcRoutingComponent.Content(it) }, + hotAccessCodeContent = { hotAccessCodeRequestComponent.Content(it) }, backHandler = backHandler, onBack = router::pop, ) @@ -120,6 +138,19 @@ internal class DefaultRoutingComponent @AssistedInject constructor( initialStack } + private fun configureProxies() { + lifecycle.subscribe( + onCreate = { + tangemHotSDKProxy.sdkState.value = TangemHotSdk.create(activity) + hotAccessCodeRequesterProxy.componentRequester.value = hotAccessCodeRequestComponent + }, + onDestroy = { + tangemHotSDKProxy.sdkState.value = null + hotAccessCodeRequesterProxy.componentRequester.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..e28474943c 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 @@ -8,6 +8,7 @@ import com.tangem.feature.referral.api.ReferralComponent import com.tangem.feature.stories.api.StoriesComponent import com.tangem.feature.usedesk.api.UsedeskComponent import com.tangem.feature.walletsettings.component.WalletSettingsComponent +import com.tangem.features.hotwallet.WalletBackupComponent import com.tangem.features.createwalletselection.CreateWalletSelectionComponent import com.tangem.features.details.component.DetailsComponent import com.tangem.features.disclaimer.api.components.DisclaimerComponent @@ -24,9 +25,10 @@ import com.tangem.features.onramp.component.* import com.tangem.features.pushnotifications.api.PushNotificationsComponent import com.tangem.features.send.v2.api.NFTSendComponent import com.tangem.features.send.v2.api.SendComponent +import com.tangem.features.send.v2.api.SendEntryPointComponent import com.tangem.features.staking.api.StakingComponent import com.tangem.features.swap.SwapComponent -import com.tangem.features.tester.api.TesterRouter +import com.tangem.features.swap.v2.api.SendWithSwapComponent import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.wallet.WalletEntryComponent import com.tangem.features.walletconnect.components.WalletConnectEntryComponent @@ -50,6 +52,7 @@ import com.tangem.features.walletconnect.components.WalletConnectEntryComponent internal class ChildFactory @Inject constructor( private val detailsComponentFactory: DetailsComponent.Factory, private val walletSettingsComponentFactory: WalletSettingsComponent.Factory, + private val walletBackupComponentFactory: WalletBackupComponent.Factory, private val disclaimerComponentFactory: DisclaimerComponent.Factory, private val manageTokensComponentFactory: ManageTokensComponent.Factory, private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory, @@ -86,7 +89,8 @@ 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 sendWithSwapComponentFactory: SendWithSwapComponent.Factory, + private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory, private val walletConnectFeatureToggles: WalletConnectFeatureToggles, ) { @@ -132,9 +136,6 @@ internal class ChildFactory @Inject constructor( componentFactory = welcomeComponentFactory, ) } - is AppRoute.TesterMenu -> { - Child.LegacyIntent(testerRouter.getEntryIntent()) - } is AppRoute.WalletSettings -> { createComponentChild( context = context, @@ -142,6 +143,15 @@ internal class ChildFactory @Inject constructor( componentFactory = walletSettingsComponentFactory, ) } + is AppRoute.WalletBackup -> { + createComponentChild( + context = context, + params = WalletBackupComponent.Params( + userWalletId = route.userWalletId, + ), + componentFactory = walletBackupComponentFactory, + ) + } is AppRoute.MarketsTokenDetails -> { createComponentChild( context = context, @@ -370,7 +380,7 @@ internal class ChildFactory @Inject constructor( is AppRoute.PushNotification -> { createComponentChild( context = context, - params = Unit, + params = PushNotificationsComponent.Params.Route(AppRoute.Home), componentFactory = pushNotificationsComponentFactory, ) } @@ -447,6 +457,26 @@ internal class ChildFactory @Inject constructor( componentFactory = addExistingWalletComponentFactory, ) } + is AppRoute.SendEntryPoint -> { + createComponentChild( + context = context, + params = SendEntryPointComponent.Params( + userWalletId = route.userWalletId, + cryptoCurrency = route.currency, + ), + componentFactory = sendEntryPointComponentFactory, + ) + } + is AppRoute.SendWithSwap -> { + createComponentChild( + context = context, + params = SendWithSwapComponent.Params( + userWalletId = route.userWalletId, + currency = route.currency, + ), + componentFactory = sendWithSwapComponentFactory, + ) + } } } } \ No newline at end of file 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/domain/card/DefaultDerivationsRepositoryTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt index 3b75f3bc36..0d4ece518c 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/card/DefaultDerivationsRepositoryTest.kt @@ -11,8 +11,8 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.ScanCardException import com.tangem.domain.common.configs.GenericCardConfig import com.tangem.domain.common.configs.MultiWalletCardConfig -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider @@ -107,7 +107,7 @@ internal class DefaultDerivationsRepositoryTest { runCatching { repository.derivePublicKeys( userWalletId = defaultUserWalletId, - currencies = MockCryptoCurrencyFactory(userWallet.scanResponse).ethereum.let(::listOf), + currencies = MockCryptoCurrencyFactory(userWallet).ethereum.let(::listOf), ) } .onSuccess { Truth.assertThat(it) } @@ -129,7 +129,7 @@ internal class DefaultDerivationsRepositoryTest { runCatching { repository.derivePublicKeys( userWalletId = defaultUserWalletId, - currencies = MockCryptoCurrencyFactory(userWallet.scanResponse).ethereum.let(::listOf), + currencies = MockCryptoCurrencyFactory(userWallet).ethereum.let(::listOf), ) } .onSuccess { error("Should throws exception") } @@ -155,7 +155,7 @@ internal class DefaultDerivationsRepositoryTest { runCatching { repository.derivePublicKeys( userWalletId = defaultUserWalletId, - currencies = MockCryptoCurrencyFactory(userWallet.scanResponse).ethereum.let(::listOf), + currencies = MockCryptoCurrencyFactory(userWallet).ethereum.let(::listOf), ) } .onSuccess { Truth.assertThat(it) } diff --git a/app/src/test/kotlin/com/tangem/tap/domain/card/MissedDerivationsFinderTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/card/MissedDerivationsFinderTest.kt index 063a5bc85b..31c1d12582 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/card/MissedDerivationsFinderTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/card/MissedDerivationsFinderTest.kt @@ -8,6 +8,7 @@ import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.test.domain.card.MockScanResponseFactory import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.common.test.domain.wallet.MockUserWalletFactory import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.common.configs.GenericCardConfig import com.tangem.domain.common.configs.MultiWalletCardConfig @@ -34,9 +35,10 @@ internal class MissedDerivationsFinderTest { fun `empty derivations for non supported blockchains`() { // Bls is not supported val scanResponse = MockScanResponseFactory.create(cardConfig = GenericCardConfig(2), derivedKeys = emptyMap()) + val userWallet = MockUserWalletFactory.create(scanResponse) val finder = MissedDerivationsFinder(scanResponse) - val currencies = MockCryptoCurrencyFactory(scanResponse).chia.let(::listOf) + val currencies = MockCryptoCurrencyFactory(userWallet).chia.let(::listOf) val actual = finder.find(currencies) Truth.assertThat(actual).isEmpty() @@ -55,9 +57,10 @@ internal class MissedDerivationsFinderTest { ), ) } + val userWallet = MockUserWalletFactory.create(scanResponse) val finder = MissedDerivationsFinder(scanResponse) - val currencies = MockCryptoCurrencyFactory(scanResponse).chiaAndEthereum + val currencies = MockCryptoCurrencyFactory(userWallet).chiaAndEthereum val actual = finder.find(currencies) Truth.assertThat(actual).containsExactly( @@ -69,9 +72,10 @@ internal class MissedDerivationsFinderTest { @Test fun `derivations for custom token`() { val scanResponse = MockScanResponseFactory.create(cardConfig = MultiWalletCardConfig, derivedKeys = emptyMap()) + val userWallet = MockUserWalletFactory.create(scanResponse) val finder = MissedDerivationsFinder(scanResponse) - val currencies = MockCryptoCurrencyFactory(scanResponse).ethereumTokenWithBinanceDerivation + val currencies = MockCryptoCurrencyFactory(userWallet).ethereumTokenWithBinanceDerivation val actual = finder.find(currencies) Truth.assertThat(actual).containsExactly( @@ -86,9 +90,10 @@ internal class MissedDerivationsFinderTest { @Test fun `derivations for cardano`() { val scanResponse = MockScanResponseFactory.create(cardConfig = MultiWalletCardConfig, derivedKeys = emptyMap()) + val userWallet = MockUserWalletFactory.create(scanResponse) val finder = MissedDerivationsFinder(scanResponse) - val currencies = MockCryptoCurrencyFactory(scanResponse).cardano.let(::listOf) + val currencies = MockCryptoCurrencyFactory(userWallet).cardano.let(::listOf) val actual = finder.find(currencies) Truth.assertThat(actual).containsExactly( @@ -111,9 +116,10 @@ internal class MissedDerivationsFinderTest { cardConfig = Wallet2CardConfig, derivedKeys = DerivedKeysMocks.ethereumDerivedKeys, ) + val userWallet = MockUserWalletFactory.create(scanResponse) val finder = MissedDerivationsFinder(scanResponse) - val currencies = MockCryptoCurrencyFactory(scanResponse).ethereum.let(::listOf) + val currencies = MockCryptoCurrencyFactory(userWallet).ethereum.let(::listOf) val actual = finder.find(currencies) Truth.assertThat(actual).isEmpty() @@ -125,9 +131,10 @@ internal class MissedDerivationsFinderTest { cardConfig = MultiWalletCardConfig, derivedKeys = DerivedKeysMocks.ethereumDerivedKeys, ) + val userWallet = MockUserWalletFactory.create(scanResponse) val finder = MissedDerivationsFinder(scanResponse) - val currencies = MockCryptoCurrencyFactory(scanResponse).ethereumAndStellar + val currencies = MockCryptoCurrencyFactory(userWallet).ethereumAndStellar val actual = finder.find(currencies) Truth.assertThat(actual).containsExactly( diff --git a/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt index fc6ecdab82..60354a079c 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt @@ -3,8 +3,8 @@ package com.tangem.tap.domain.userWalletList.implementation import com.google.common.truth.Truth import com.tangem.common.test.domain.card.MockScanResponseFactory import com.tangem.domain.common.configs.GenericCardConfig -import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId import io.mockk.mockk import org.junit.Test import org.junit.runner.RunWith 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 "