diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6502728cd8..19dd55b0e1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -84,6 +84,7 @@ dependencies { implementation(projects.domain.core) implementation(projects.domain.card) implementation(projects.domain.demo) + implementation(projects.domain.demo.models) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) implementation(projects.domain.settings) @@ -121,6 +122,8 @@ dependencies { implementation(projects.domain.notifications.toggles) implementation(projects.domain.swap.models) implementation(projects.domain.swap) + implementation(projects.domain.walletManager) + implementation(projects.domain.walletManager.models) implementation(projects.common) implementation(projects.common.routing) @@ -134,7 +137,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) @@ -168,6 +170,7 @@ dependencies { implementation(projects.data.blockaid) implementation(projects.data.notifications) implementation(projects.data.swap) + implementation(projects.data.walletManager) /** Features */ implementation(projects.features.referral.impl) @@ -229,6 +232,8 @@ dependencies { implementation(projects.features.welcome.impl) implementation(projects.features.createWalletSelection.api) implementation(projects.features.createWalletSelection.impl) + implementation(projects.features.home.api) + implementation(projects.features.home.impl) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) @@ -281,6 +286,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 +307,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..f1bf225ead --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/annotations/ApiEnv.kt @@ -0,0 +1,26 @@ +package com.tangem.common.annotations + +import com.tangem.datasource.api.common.config.ApiConfig +import com.tangem.datasource.api.common.config.ApiEnvironment + +/** + * Single API environment configuration. + * + * @property apiConfigId the ID of the API configuration + * @property environment the API environment to be used (defaults to [ApiEnvironment.MOCK]) + */ +annotation class ApiEnvConfig( + val apiConfigId: ApiConfig.ID, + val environment: ApiEnvironment = ApiEnvironment.MOCK, +) + +/** + * Annotation to specify the API environment configurations for a class or function. + * + * @property value array of API environment configurations + */ +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class ApiEnv( + vararg val value: ApiEnvConfig, +) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/common/extensions/BaseTestCaseExt.kt b/app/src/androidTest/kotlin/com/tangem/common/extensions/BaseTestCaseExt.kt new file mode 100644 index 0000000000..bff5addff5 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/extensions/BaseTestCaseExt.kt @@ -0,0 +1,15 @@ +package com.tangem.common.extensions + +import com.tangem.common.BaseTestCase + +fun BaseTestCase.swipeToCloseApp() { + + device.uiDevice.swipe( + device.uiDevice.displayWidth / 2, + device.uiDevice.displayHeight / 2, + device.uiDevice.displayWidth / 2, + device.uiDevice.displayHeight / 30, + 15 + ) + +} \ 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..0b0f1a32d0 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/rules/ApiEnvironmentRule.kt @@ -0,0 +1,132 @@ +package com.tangem.common.rules + +import androidx.test.platform.app.InstrumentationRegistry +import com.tangem.common.annotations.ApiEnv +import com.tangem.datasource.api.common.config.ApiConfig +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 annotations on the test method/class + * or via an instrumentation argument. + * + * This rule supports @ApiEnv annotation with a map of API configs to configure different environments. + * For any ApiConfig.ID not specified in annotations, MOCK environment will be used by default. + */ +class ApiEnvironmentRule : TestRule { + + private var targetEnvironments: Map = emptyMap() + + /** + * 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.setupEnvironments() + } + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + targetEnvironments = determineEnvironments(description) + base.evaluate() + } + } + } + + private fun determineEnvironments(description: Description): Map { + val instrumentationArgs = InstrumentationRegistry.getArguments() + val envConfigArg = instrumentationArgs.getString(ENV_CONFIGS_ARGUMENT) + + if (!envConfigArg.isNullOrEmpty()) { + return parseEnvironmentConfigs(envConfigArg) + } + + val methodEnvironments = collectApiEnvAnnotations(description) + if (methodEnvironments.isNotEmpty()) { + return DEFAULT_API_CONFIGS.associateWith { ApiEnvironment.MOCK } + methodEnvironments + } + + val classEnvironments = collectApiEnvAnnotations(description.testClass) + if (classEnvironments.isNotEmpty()) { + return DEFAULT_API_CONFIGS.associateWith { ApiEnvironment.MOCK } + classEnvironments + } + + return DEFAULT_API_CONFIGS.associateWith { ApiEnvironment.MOCK } + } + + private fun parseEnvironmentConfigs(envConfigArg: String): Map { + return try { + val parsedConfigs = envConfigArg.split(",") + .map { it.trim() } + .mapNotNull { configPair -> + val parts = configPair.split("=").map { it.trim() } + if (parts.size == 2) { + try { + val apiConfigId = ApiConfig.ID.valueOf(parts[0]) + val environment = ApiEnvironment.valueOf(parts[1]) + apiConfigId to environment + } catch (e: IllegalArgumentException) { + Timber.w("Invalid config or environment: $configPair") + null + } + } else { + Timber.w("Invalid config format: $configPair. Expected format: 'ConfigId=Environment'") + null + } + } + .toMap() + + DEFAULT_API_CONFIGS.associateWith { ApiEnvironment.MOCK } + parsedConfigs + } catch (e: Exception) { + Timber.w("Failed to parse environment configs: $envConfigArg") + DEFAULT_API_CONFIGS.associateWith { ApiEnvironment.MOCK } + } + } + + private fun collectApiEnvAnnotations(description: Description): Map { + return description.getAnnotation(ApiEnv::class.java)?.value + ?.associate { it.apiConfigId to it.environment } ?: emptyMap() + } + + private fun collectApiEnvAnnotations(testClass: Class<*>): Map { + return testClass.getAnnotation(ApiEnv::class.java)?.value + ?.associate { it.apiConfigId to it.environment } ?: emptyMap() + } + + private fun MutableApiConfigsManager.setupEnvironments() { + require(targetEnvironments.isNotEmpty()) { "Target environments map is empty" } + + runBlocking { + targetEnvironments.forEach { (apiConfigId, environment) -> + changeEnvironment(apiConfigId.name, environment) + Timber.i("$apiConfigId environment set to: ${environment.name}") + } + } + } + + private companion object { + const val ENV_CONFIGS_ARGUMENT = "testEnvironmentConfigs" + + val DEFAULT_API_CONFIGS = listOf( + ApiConfig.ID.TangemTech, + ApiConfig.ID.Express, + ApiConfig.ID.TangemPay, + ) + } +} \ 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/common/utils/WireMockUtils.kt b/app/src/androidTest/kotlin/com/tangem/common/utils/WireMockUtils.kt new file mode 100644 index 0000000000..eb4bc13f9e --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/common/utils/WireMockUtils.kt @@ -0,0 +1,96 @@ +package com.tangem.common.utils + +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import timber.log.Timber +import java.io.IOException + +/** + * Method uses to set WireMock scenario state + */ +fun setWireMockScenarioState( + scenarioName: String, + state: String, + baseUrl: String = "[REDACTED_ENV_URL]" +): Boolean { + val client = OkHttpClient() + val json = """{"state": "$state"}""" + val mediaType = "application/json".toMediaType() + + val request = Request.Builder() + .url("$baseUrl/__admin/scenarios/$scenarioName/state") + .put(json.toRequestBody(mediaType)) + .build() + + return try { + client.newCall(request).execute().use { response -> + val body = response.body?.string() ?: "" + Timber.d("WireMock scenario request URL: ${request.url}") + Timber.d("WireMock scenario request body: $json") + Timber.d("WireMock scenario response: ${response.code} - ${response.message}") + Timber.d("WireMock scenario response body: $body") + response.isSuccessful + } + } catch (e: IOException) { + Timber.e(e, "WireMock scenario error") + false + } +} + +/** + * Method checks accessibility of WireMock + */ +fun checkWireMockStatus(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean { + val client = OkHttpClient() + val request = Request.Builder() + .url("$baseUrl/__admin/scenarios") + .get() + .build() + + return try { + client.newCall(request).execute().use { response -> + val body = response.body?.string() ?: "" + Timber.d("WireMock status check: ${response.code}") + Timber.d("Available scenarios: $body") + response.isSuccessful + } + } catch (e: IOException) { + Timber.e(e, "WireMock not accessible") + false + } +} + +/** + * Method to reset all WireMock scenarios + */ +fun resetWireMockScenarios(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean { + Timber.i("=== WireMock Scenarios Reset ===") + Timber.i("Base URL: $baseUrl") + + val client = OkHttpClient() + val url = "$baseUrl/__admin/scenarios/reset" + Timber.i("Request URL: $url") + + val request = Request.Builder() + .url(url) + .post("".toRequestBody()) + .build() + + return try { + Timber.d("Sending reset request...") + client.newCall(request).execute().use { response -> + Timber.d("Response code: ${response.code}") + Timber.d("Response message: ${response.message}") + val responseBody = response.body?.string() ?: "" + Timber.d("Response body: $responseBody") + + val isSuccessful = response.isSuccessful + Timber.d("Is successful: $isSuccessful") + isSuccessful + } + } catch (e: IOException) { + Timber.e(e, "Exception during reset") + false + } +} \ 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/BuyTokenDetailsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenDetailsPageObject.kt new file mode 100644 index 0000000000..4193f3ab8b --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenDetailsPageObject.kt @@ -0,0 +1,101 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.R +import com.tangem.core.ui.test.BaseButtonTestTags +import com.tangem.core.ui.test.BuyTokenDetailsScreenTestTags +import com.tangem.core.ui.test.NotificationTestTags +import com.tangem.core.ui.test.TopAppBarTestTags +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.kakao.common.utilities.getResourceString +import androidx.compose.ui.test.hasTestTag as withTestTag + +class BuyTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val topBarTitle: KNode = child { + hasTestTag(TopAppBarTestTags.TITLE) + useUnmergedTree = true + } + + val topBarMoreButton: KNode = child { + hasTestTag(TopAppBarTestTags.MORE_BUTTON) + useUnmergedTree = true + } + + val topBarCloseButton: KNode = child { + hasTestTag(TopAppBarTestTags.CLOSE_BUTTON) + useUnmergedTree = true + } + + val errorNotificationTitle: KNode = child { + hasTestTag(NotificationTestTags.TITLE) + hasText(getResourceString(R.string.common_error)) + useUnmergedTree = true + } + + val errorNotificationText: KNode = child { + hasTestTag(NotificationTestTags.TEXT) + hasText(getResourceString(R.string.common_unknown_error)) + useUnmergedTree = true + } + + val refreshButton: KNode = child { + hasTestTag(BaseButtonTestTags.BUTTON) + hasText(getResourceString(R.string.warning_button_refresh)) + } + + val fiatCurrencyIcon: KNode = child { + hasTestTag(BuyTokenDetailsScreenTestTags.FIAT_CURRENCY_ICON) + useUnmergedTree = true + } + + val expandFiatListButton: KNode = child { + hasTestTag(BuyTokenDetailsScreenTestTags.EXPAND_FIAT_LIST_BUTTON) + useUnmergedTree = true + } + + val fiatAmountTextField: KNode = child { + hasParent(withTestTag(BuyTokenDetailsScreenTestTags.FIAT_AMOUNT_TEXT_FIELD)) + useUnmergedTree = true + } + + val tokenAmountField: KNode = child { + hasParent(withTestTag(BuyTokenDetailsScreenTestTags.TOKEN_AMOUNT)) + useUnmergedTree = true + } + + val providerLoadingTitle: KNode = child { + hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_LOADING_TITLE) + } + + val providerLoadingText: KNode = child { + hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_LOADING_TEXT) + } + + val providerTitle: KNode = child { + hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_TITLE) + useUnmergedTree = true + } + + val providerText: KNode = child { + hasTestTag(BuyTokenDetailsScreenTestTags.PROVIDER_TEXT) + useUnmergedTree = true + } + + val buyButton: KNode = child { + hasTestTag(BaseButtonTestTags.BUTTON) + hasText(getResourceString(R.string.common_buy)) + } + + val toSBlock: KNode = child { + hasTestTag(BuyTokenDetailsScreenTestTags.TOS_BLOCK) + useUnmergedTree = true + } +} + +internal fun BaseTestCase.onBuyTokenDetailsScreen(function: BuyTokenDetailsPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenFiatListPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenFiatListPageObject.kt new file mode 100644 index 0000000000..9e453330b3 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenFiatListPageObject.kt @@ -0,0 +1,38 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.common.utils.LazyListItemNode +import com.tangem.core.ui.test.BuyTokenFiatListTestTags +import com.tangem.core.ui.utils.LazyListItemPositionSemantics +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 + +class BuyTokenFiatListPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + + private val lazyList = KLazyListNode( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(BuyTokenFiatListTestTags.LAZY_LIST) }, + itemTypeBuilder = { itemType(::LazyListItemNode) }, + positionMatcher = { position -> + SemanticsMatcher.expectValue( + LazyListItemPositionSemantics, + position + ) + } + ) + + fun fiatListItemWithTitle(title: String): KNode { + return lazyList.child { + hasText(title) + } + } +} + +internal fun BaseTestCase.onBuyTokenFiatListBottomSheet(function: BuyTokenFiatListPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenPageObject.kt new file mode 100644 index 0000000000..1dbe1d3ba3 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/BuyTokenPageObject.kt @@ -0,0 +1,54 @@ +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.utils.LazyListItemNode +import com.tangem.core.ui.R +import com.tangem.core.ui.test.BuyTokenScreenTestTags +import com.tangem.core.ui.test.TokenElementsTestTags +import com.tangem.core.ui.test.TopAppBarTestTags +import com.tangem.core.ui.utils.LazyListItemPositionSemantics +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 + +class BuyTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val topAppBarTitle: KNode = child { + hasTestTag(TopAppBarTestTags.TITLE) + hasText(getResourceString(R.string.common_buy)) + useUnmergedTree = true + } + + private val lazyList = KLazyListNode( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(BuyTokenScreenTestTags.LAZY_LIST) }, + itemTypeBuilder = { itemType(::LazyListItemNode) }, + positionMatcher = { position -> + SemanticsMatcher.expectValue( + LazyListItemPositionSemantics, + position + ) + } + ) + + @OptIn(ExperimentalTestApi::class) + fun tokenWithTitleAndFiatAmount(tokenTitle: String): KNode { + return lazyList.childWith { + hasTestTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM) + hasText(tokenTitle) + useUnmergedTree = true + }.child { + hasTestTag(TokenElementsTestTags.TOKEN_FIAT_AMOUNT) + useUnmergedTree = true + } + } +} + +internal fun BaseTestCase.onBuyTokenScreen(function: BuyTokenPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file 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..5bac035afc --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/DialogPageObject.kt @@ -0,0 +1,37 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.R +import com.tangem.core.ui.test.BaseButtonTestTags +import com.tangem.core.ui.test.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(BaseButtonTestTags.BUTTON) + hasText(getResourceString(R.string.common_cancel)) + } + + val hideButton: KNode = child { + hasTestTag(BaseButtonTestTags.BUTTON) + hasText(getResourceString(R.string.token_details_hide_alert_hide)) + } + + val confirmButton: KNode = child { + hasTestTag(BaseButtonTestTags.BUTTON) + hasText(getResourceString(R.string.common_confirm)) + } +} + +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..9e43faf303 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/DisclaimerPageObject.kt @@ -0,0 +1,34 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.DisclaimerScreenTestTags +import com.tangem.core.ui.test.TopAppBarTestTags +import com.tangem.features.disclaimer.impl.R +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.kakao.common.utilities.getResourceString + +class DisclaimerPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(DisclaimerScreenTestTags.SCREEN_CONTAINER) } + ) { + + val title: KNode = child { + hasTestTag(TopAppBarTestTags.TITLE) + hasText(getResourceString(R.string.disclaimer_title)) + } + + val webView: KNode = child { + hasTestTag(DisclaimerScreenTestTags.WEB_VIEW) + } + + 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..ce3a3181b3 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/MainScreenPageObject.kt @@ -0,0 +1,124 @@ +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)) + } + + val buyButton: KNode = child { + hasTestTag(MainScreenTestTags.MULTI_CURRENCY_ACTION_BUTTON) + hasText(getResourceString(R.string.common_buy)) + } + + /** + * 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/ResidenceSettingsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/ResidenceSettingsPageObject.kt new file mode 100644 index 0000000000..c25bad4120 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/ResidenceSettingsPageObject.kt @@ -0,0 +1,46 @@ +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.ResidenceSettingsScreenTestTags +import com.tangem.core.ui.test.TopAppBarTestTags +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.kakao.common.utilities.getResourceString +import com.tangem.features.onramp.impl.R as OnrampImplR + +class ResidenceSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val topBarTitle: KNode = child { + hasTestTag(TopAppBarTestTags.TITLE) + hasText(getResourceString(R.string.onramp_settings_title)) + useUnmergedTree = true + } + + val topBarCloseButton: KNode = child { + hasTestTag(TopAppBarTestTags.CLOSE_BUTTON) + useUnmergedTree = true + } + + val residenceButton: KNode = child { + hasText(getResourceString(OnrampImplR.string.onramp_settings_residence)) + useUnmergedTree = true + } + + val countryName: KNode = child { + hasTestTag(ResidenceSettingsScreenTestTags.COUNTRY_NAME) + useUnmergedTree = true + } + + val residenceSettingsDescription: KNode = child { + hasText(getResourceString(OnrampImplR.string.onramp_settings_residence_description)) + useUnmergedTree = true + } + +} + +internal fun BaseTestCase.onResidenceSettingsScreen(function: ResidenceSettingsPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SelectCountryPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SelectCountryPageObject.kt new file mode 100644 index 0000000000..b3a6a9d573 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/SelectCountryPageObject.kt @@ -0,0 +1,59 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.common.utils.LazyListItemNode +import com.tangem.core.ui.test.SelectCountryBottomSheetTestTags +import com.tangem.core.ui.utils.LazyListItemPositionSemantics +import com.tangem.features.onramp.impl.R +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode +import io.github.kakaocup.kakao.common.utilities.getResourceString +import androidx.compose.ui.test.hasText as withText +import androidx.compose.ui.test.hasTestTag as withTestTag + + +class SelectCountryPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + + private val lazyList = KLazyListNode( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(SelectCountryBottomSheetTestTags.LAZY_LIST) }, + itemTypeBuilder = { itemType(::LazyListItemNode) }, + positionMatcher = { position -> + SemanticsMatcher.expectValue( + LazyListItemPositionSemantics, + position + ) + } + ) + + val searchBar: KNode = child { + hasTestTag(SelectCountryBottomSheetTestTags.SEARCH_BAR) + useUnmergedTree = true + } + + fun countryWithNameAndIcon(name: String): KNode { + return lazyList.child { + hasText(name) + hasAnySibling(withTestTag(SelectCountryBottomSheetTestTags.COUNTRY_ICON)) + useUnmergedTree = true + } + } + + fun unavailableCountryWithNameAndIcon(name: String): KNode { + return lazyList.child { + hasText(name) + hasAnySibling(withText(getResourceString(R.string.onramp_country_unavailable))) + hasAnySibling(withTestTag(SelectCountryBottomSheetTestTags.UNAVAILABLE_COUNTRY_ICON)) + useUnmergedTree = true + } + } +} + +internal fun BaseTestCase.onSelectCountryBottomSheet(function: SelectCountryPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SelectPaymentMethodPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SelectPaymentMethodPageObject.kt new file mode 100644 index 0000000000..0739883572 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/SelectPaymentMethodPageObject.kt @@ -0,0 +1,50 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.common.utils.LazyListItemNode +import com.tangem.core.ui.test.SelectPaymentMethodBottomSheetTestTags +import com.tangem.core.ui.test.TopAppBarTestTags +import com.tangem.core.ui.utils.LazyListItemPositionSemantics +import com.tangem.features.onramp.impl.R +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode +import io.github.kakaocup.kakao.common.utilities.getResourceString +import androidx.compose.ui.test.hasTestTag as withTestTag +import androidx.compose.ui.test.hasText as withText + +class SelectPaymentMethodPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + + private val lazyList = KLazyListNode( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag(SelectPaymentMethodBottomSheetTestTags.LAZY_LIST) }, + itemTypeBuilder = { itemType(::LazyListItemNode) }, + positionMatcher = { position -> + SemanticsMatcher.expectValue( + LazyListItemPositionSemantics, + position + ) + } + ) + + val title: KNode = child { + hasTestTag(TopAppBarTestTags.TITLE) + hasText(getResourceString(R.string.onramp_pay_with)) + } + + fun paymentMethodWithNameAndIcon(name: String): KNode { + return lazyList.child { + hasAnyDescendant(withText(name)) + hasAnyDescendant(withTestTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD_ICON)) + useUnmergedTree = true + } + } +} + +internal fun BaseTestCase.onSelectPaymentMethodBottomSheet(function: SelectPaymentMethodPageObject.() -> Unit) = + onComposeScreen(composeTestRule, function) \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SelectProviderPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SelectProviderPageObject.kt new file mode 100644 index 0000000000..2c8a050019 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/screens/SelectProviderPageObject.kt @@ -0,0 +1,91 @@ +package com.tangem.screens + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import com.tangem.common.BaseTestCase +import com.tangem.core.ui.test.SelectProviderBottomSheetTestTags +import com.tangem.features.onramp.impl.R +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen +import io.github.kakaocup.compose.node.element.KNode +import io.github.kakaocup.kakao.common.utilities.getResourceString +import androidx.compose.ui.test.hasText as withText + +class SelectProviderPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : + ComposeScreen(semanticsProvider = semanticsProvider) { + + val title: KNode = child { + hasText(getResourceString(R.string.onramp_choose_provider_title_hint)) + useUnmergedTree = true + } + + val paymentMethodIcon: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_ICON) + useUnmergedTree = true + } + + val paymentMethodTitle: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_NAME) + useUnmergedTree = true + } + + val paymentMethodName: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_NAME) + useUnmergedTree = true + } + + val paymentMethodExpandButton: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.PAYMENT_METHOD_EXPAND_BUTTON) + useUnmergedTree = true + } + + val availableProviderItem: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_ITEM) + useUnmergedTree = true + } + + val availableProviderName: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_NAME) + useUnmergedTree = true + } + + val tokenAmount: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.TOKEN_AMOUNT) + useUnmergedTree = true + } + + val unavailableProviderItem: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.UNAVAILABLE_PROVIDER_ITEM) + useUnmergedTree = true + } + + val unavailableProviderName: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.UNAVAILABLE_PROVIDER_NAME) + useUnmergedTree = true + } + + val moreProvidersIcon: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.MORE_PROVIDERS_ICON) + useUnmergedTree = true + } + + val moreProvidersText: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.MORE_PROVIDERS_TEXT) + useUnmergedTree = true + } + + val bestRateLabel: KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.BEST_RATE_LABEL) + useUnmergedTree = true + } + + fun availableProviderWithName(name: String, tokenAmount: String, rate: String): KNode = child { + hasTestTag(SelectProviderBottomSheetTestTags.AVAILABLE_PROVIDER_ITEM) + hasAnyChild(withText(name)) + hasAnyChild(withText(tokenAmount)) + hasAnyChild(withText(rate)) + useUnmergedTree = true + } +} + +internal fun BaseTestCase.onSelectProviderBottomSheet(function: SelectProviderPageObject.() -> 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/BuyTokenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/BuyTokenTest.kt new file mode 100644 index 0000000000..1c05fa58dc --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/BuyTokenTest.kt @@ -0,0 +1,477 @@ +package com.tangem.tests + +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.clickWithAssertion +import com.tangem.common.utils.resetWireMockScenarios +import com.tangem.common.utils.setWireMockScenarioState +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 BuyTokenTest : BaseTestCase() { + + @AllureId("3478") + @DisplayName("Onramp: error in providers loading") + @Test + fun errorInProvidersLoadingTest() { + setupHooks( + additionalAfterSection = { + resetWireMockScenarios() + } + ).run { + val tokenTitle = "Bitcoin" + val balance = "$184.85" + + resetWireMockScenarios() + + step("Setup WireMock scenario for 'Error' state") { + setWireMockScenarioState("payment_methods", "Error") + } + 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 'Buy' button") { + onMainScreen { buyButton.clickWithAssertion() } + } + step("Click on token with name: '$tokenTitle'") { + onBuyTokenScreen { + topAppBarTitle.assertIsDisplayed() + tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion() + } + } + step("Assert error notification title is displayed") { + onBuyTokenDetailsScreen { errorNotificationTitle.assertIsDisplayed() } + } + step("Assert error notification text is displayed") { + onBuyTokenDetailsScreen { errorNotificationText.assertIsDisplayed() } + } + step("Assert 'Refresh' button is displayed and clickable") { + onBuyTokenDetailsScreen { refreshButton.clickWithAssertion() } + } + } + } + + @AllureId("2565") + @DisplayName("Onramp: validate currency selector") + @Test + fun validateCurrencySelectorTest() { + setupHooks().run { + val tokenTitle = "Polygon" + val balance = "$184.85" + val popularFiatsTitle = "Popular Fiats" + val otherCurrenciesTitle = "Other currencies" + val australianDollar = "AUD" + val fiatAmount = "1" + val tokenAmount = "POL 488.24938338" + + 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 'Buy' button") { + onMainScreen { buyButton.clickWithAssertion() } + } + step("Click on token with name: '$tokenTitle'") { + onBuyTokenScreen { + topAppBarTitle.assertIsDisplayed() + tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion() + } + } + step("Click on 'Confirm' button in 'Dialog'") { + onDialog { confirmButton.clickWithAssertion() } + } + step("Write fiat amount = '$fiatAmount'") { + onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) } + } + step("Assert 'Provider loading block' is displayed") { + onBuyTokenDetailsScreen { + providerLoadingTitle.assertIsDisplayed() + providerLoadingText.assertIsDisplayed() + } + } + step("Assert 'Provider block' is displayed") { + onBuyTokenDetailsScreen { + providerTitle.assertIsDisplayed() + providerText.assertIsDisplayed() + } + } + step("Assert token amount = '$tokenAmount'") { + onBuyTokenDetailsScreen { + tokenAmountField.assertTextContains(tokenAmount) + } + } + step("Fiat currency icon is displayed") { + onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() } + } + step("Click on 'Expand fiat list' button") { + onBuyTokenDetailsScreen { expandFiatListButton.clickWithAssertion() } + } + step("Assert '$popularFiatsTitle' is displayed") { + onBuyTokenFiatListBottomSheet { + fiatListItemWithTitle(popularFiatsTitle).assertIsDisplayed() + } + } + step("Assert '$otherCurrenciesTitle' is displayed") { + onBuyTokenFiatListBottomSheet { + fiatListItemWithTitle(otherCurrenciesTitle).assertIsDisplayed() + } + } + step("Click on fiat with title: '$australianDollar'") { + onBuyTokenFiatListBottomSheet { + fiatListItemWithTitle(australianDollar).performClick() + } + } + step("Assert new fiat currency: '$australianDollar' is displayed") { + onBuyTokenDetailsScreen { + fiatAmountTextField.assertTextContains(australianDollar + fiatAmount) + } + } + step("Assert token amount = '$tokenAmount'") { + onBuyTokenDetailsScreen { + tokenAmountField.assertTextContains(tokenAmount) + } + } + } + } + + @AllureId("2566") + @DisplayName("Onramp: validate 'Buy token' screen") + @Test + fun validateBuyTokenScreenTest() { + setupHooks().run { + val tokenTitle = "Polygon" + val balance = "$184.85" + val euro = "EUR" + val fiatAmount = "1" + val tokenAmount = "POL 488.24938338" + + 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 'Buy' button") { + onMainScreen { buyButton.clickWithAssertion() } + } + step("Click on token with name: '$tokenTitle'") { + onBuyTokenScreen { + topAppBarTitle.assertIsDisplayed() + tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion() + } + } + step("Click on 'Confirm' button in 'Dialog'") { + onDialog { confirmButton.clickWithAssertion() } + } + step("Assert 'Buy Token' title is displayed") { + onBuyTokenDetailsScreen { topBarTitle.assertTextContains("Buy $tokenTitle") } + } + step("Assert 'More button' in top bar is displayed") { + onBuyTokenDetailsScreen { topBarMoreButton.assertIsDisplayed() } + } + step("Write fiat amount = '$fiatAmount'") { + onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) } + } + step("Assert fiat amount = '$fiatAmount'") { + onBuyTokenDetailsScreen { fiatAmountTextField.assertTextContains(euro + fiatAmount) } + } + step("Assert 'Provider loading block' is displayed") { + onBuyTokenDetailsScreen { + providerLoadingTitle.assertIsDisplayed() + providerLoadingText.assertIsDisplayed() + } + } + step("Assert 'Provider block' is displayed") { + onBuyTokenDetailsScreen { + providerTitle.assertIsDisplayed() + providerText.assertIsDisplayed() + } + } + step("Assert token amount = '$tokenAmount'") { + onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) } + } + step("Assert 'ToS' block is displayed") { + onBuyTokenDetailsScreen { toSBlock.assertIsDisplayed()} + } + step("Assert 'Buy' button is displayed") { + onBuyTokenDetailsScreen { buyButton.assertIsDisplayed()} + } + step("Assert 'Close' button in top bar is displayed") { + onBuyTokenDetailsScreen { topBarCloseButton.assertIsDisplayed() } + } + } + } + + @AllureId("2563") + @DisplayName("Onramp: validate 'Residence' settings screen") + @Test + fun validateResidenceSettingsScreenTest() { + setupHooks().run { + val tokenTitle = "Polygon" + val balance = "$184.85" + val country = "Albania" + val unavailableCountry = "Lebanon" + + 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 'Buy' button") { + onMainScreen { buyButton.clickWithAssertion() } + } + step("Click on token with name: '$tokenTitle'") { + onBuyTokenScreen { + topAppBarTitle.assertIsDisplayed() + tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion() + } + } + step("Click on 'Confirm' button in 'Dialog'") { + onDialog { confirmButton.clickWithAssertion() } + } + step("Assert 'Buy $tokenTitle' title is displayed") { + onBuyTokenDetailsScreen { topBarTitle.assertTextContains("Buy $tokenTitle") } + } + step("Click 'More' button in tab bar") { + onBuyTokenDetailsScreen { topBarMoreButton.clickWithAssertion() } + } + step("Assert 'Residence Settings' screen top bar title is displayed") { + onResidenceSettingsScreen { topBarTitle.assertIsDisplayed() } + } + step("Assert 'Residence Settings' screen top bar 'Close' button is displayed") { + onResidenceSettingsScreen { topBarCloseButton.assertIsDisplayed() } + } + step("Assert 'Residence' button is displayed on 'Residence Settings' screen") { + onResidenceSettingsScreen { residenceButton.assertIsDisplayed() } + } + step("Assert country name is displayed on 'Residence Settings' screen") { + onResidenceSettingsScreen { countryName.assertIsDisplayed() } + } + step("Assert residence settings description is displayed on 'Residence Settings' screen") { + onResidenceSettingsScreen { residenceSettingsDescription.assertIsDisplayed() } + } + step("Click 'Residence button'") { + onResidenceSettingsScreen { residenceButton.clickWithAssertion() } + } + step("Assert 'Search bar' is displayed") { + onSelectCountryBottomSheet { searchBar.assertIsDisplayed() } + } + step("Type unavailable country name: '$unavailableCountry' in 'Search bar'") { + onSelectCountryBottomSheet { searchBar.performTextReplacement(unavailableCountry) } + } + step("Unavailable country: '$unavailableCountry' is displayed") { + onSelectCountryBottomSheet { unavailableCountryWithNameAndIcon(unavailableCountry).assertIsDisplayed() } + } + step("Type country name: '$country' in 'Search bar'") { + onSelectCountryBottomSheet { searchBar.performTextReplacement(country) } + } + step("Available country: '$country' is displayed") { + onSelectCountryBottomSheet { countryWithNameAndIcon(country).assertIsDisplayed() } + } + step("Click on country: '$country'") { + onSelectCountryBottomSheet { countryWithNameAndIcon(country).clickWithAssertion() } + } + step("Assert country: '$country' is displayed on 'Residence Settings' screen") { + onResidenceSettingsScreen { countryName.assertTextContains(country) } + } + } + } + + @AllureId("2570") + @DisplayName("Onramp: validate 'Select provider' bottom sheet") + @Test + fun validateProvidersScreenTest() { + setupHooks().run { + val tokenTitle = "Polygon" + val balance = "$184.85" + val paymentMethod = "Card" + val fiatAmount = "1" + val providerNameMercuryo = "Mercuryo" + val providerNameSimplex = "Simplex" + val tokenAmount = "POL 488.24938338" + val bestRate = "Best rate" + val rate = "-0.00%" + + 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 'Buy' button") { + onMainScreen { buyButton.clickWithAssertion() } + } + step("Click on token with name: '$tokenTitle'") { + onBuyTokenScreen { + topAppBarTitle.assertIsDisplayed() + tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion() + } + } + step("Click on 'Confirm' button in 'Dialog'") { + onDialog { confirmButton.clickWithAssertion() } + } + step("Write fiat amount = '$fiatAmount'") { + onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) } + } + step("Assert 'Provider block' is displayed") { + onBuyTokenDetailsScreen { + providerTitle.assertIsDisplayed() + providerText.assertIsDisplayed() + } + } + step("Open 'Select Provider' bottom sheet") { + onBuyTokenDetailsScreen { providerTitle.performClick() } + } + step("Assert available provider name is displayed") { + onSelectProviderBottomSheet { + flakySafely(timeoutMs = 20_000) { + availableProviderItem.assertIsDisplayed() + } + } + } + step("Assert unavailable provider name is displayed") { + onSelectProviderBottomSheet { + flakySafely(timeoutMs = 20_000) { + unavailableProviderItem.assertIsDisplayed() + } + } + } + step("Click on 'Expand payment methods' button") { + onSelectProviderBottomSheet { paymentMethodExpandButton.clickWithAssertion() } + } + step("Click on payment method: '$paymentMethod'") { + onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(paymentMethod).clickWithAssertion() } + } + step("Assert 'Select Provider' bottom sheet title is displayed") { + onSelectProviderBottomSheet { title.assertIsDisplayed() } + } + step("Assert payment method icon is displayed") { + onSelectProviderBottomSheet { paymentMethodIcon.assertIsDisplayed() } + } + step("Assert payment method title is displayed") { + onSelectProviderBottomSheet { paymentMethodTitle.assertIsDisplayed() } + } + step("Assert payment method name is displayed") { + onSelectProviderBottomSheet { paymentMethodName.assertIsDisplayed() } + } + step("Assert provider with name: '$providerNameMercuryo' and rate: '$bestRate' is displayed") { + onSelectProviderBottomSheet { + availableProviderWithName(providerNameMercuryo, tokenAmount, bestRate).assertIsDisplayed() + } + } + step("Assert provider with name: '$providerNameSimplex' and rate: '$rate' is displayed") { + onSelectProviderBottomSheet { + availableProviderWithName(providerNameSimplex, tokenAmount, rate).assertIsDisplayed() + } + } + step("Assert 'More providers' icon is displayed") { + onSelectProviderBottomSheet { moreProvidersIcon.assertIsDisplayed() } + } + step("Assert 'More providers' text is displayed") { + onSelectProviderBottomSheet { moreProvidersText.assertIsDisplayed() } + } + step("Assert 'Best rate' label is displayed") { + onSelectProviderBottomSheet { bestRateLabel.assertIsDisplayed() } + } + } + } + + @AllureId("3479") + @DisplayName("Onramp: validate 'Select payment method' bottom sheet") + @Test + fun validatePaymentMethodScreenTest() { + setupHooks().run { + val tokenTitle = "Polygon" + val balance = "$184.85" + val card = "Card" + val googlePay = "Google Pay" + val invoiceRevolutPay = "Invoice Revolut Pay" + val sepa = "Sepa" + val fiatAmount = "1" + + 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 'Buy' button") { + onMainScreen { buyButton.clickWithAssertion() } + } + step("Click on token with name: '$tokenTitle'") { + onBuyTokenScreen { + topAppBarTitle.assertIsDisplayed() + tokenWithTitleAndFiatAmount(tokenTitle).clickWithAssertion() + } + } + step("Click on 'Confirm' button in 'Dialog'") { + onDialog { confirmButton.clickWithAssertion() } + } + step("Write fiat amount = '$fiatAmount'") { + onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) } + } + step("Assert 'Provider block' is displayed") { + onBuyTokenDetailsScreen { + providerTitle.assertIsDisplayed() + providerText.assertIsDisplayed() + } + } + step("Open 'Select Provider' bottom sheet") { + onBuyTokenDetailsScreen { providerTitle.performClick() } + } + step("Click on 'Expand payment methods' button") { + onSelectProviderBottomSheet { paymentMethodExpandButton.clickWithAssertion() } + } + step("Assert 'Select Payment Method' bottom sheet title is displayed") { + onSelectPaymentMethodBottomSheet { title.assertIsDisplayed() } + } + step("Assert payment method: '$card' is displayed") { + onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(card).assertIsDisplayed() } + } + step("Assert payment method: '$googlePay' is displayed") { + onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(googlePay).assertIsDisplayed() } + } + step("Assert payment method: '$invoiceRevolutPay' is displayed") { + onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(invoiceRevolutPay).assertIsDisplayed() } + } + step("Assert payment method: '$sepa' is displayed") { + onSelectPaymentMethodBottomSheet { paymentMethodWithNameAndIcon(sepa).assertIsDisplayed() } + } + step("Press 'Back' button") { + onSelectPaymentMethodBottomSheet { device.uiDevice.pressBack() } + } + step("Assert 'Select Provider' bottom sheet title is displayed") { + onSelectProviderBottomSheet { title.assertIsDisplayed() } + } + } + } + +} \ 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 53% rename from app/src/androidTest/kotlin/com/tangem/tests/DetailsScreenTest.kt rename to app/src/androidTest/kotlin/com/tangem/tests/DetailsTest.kt index dd86a046cc..805c4bfe72 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() = + // @Test + 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..6bbf318ec7 --- /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 = "$184.85" + 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..6d89ee640d --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt @@ -0,0 +1,214 @@ +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" + val balance = "$184.85" + 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("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" + val balance = "$184.85" + 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("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(polygonTitle, 1).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 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(polygonTitle, 0).assertIsDisplayed() + tokenWithTitleAndPosition(ethereumTitle, 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..63a4706cb0 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt @@ -1,37 +1,34 @@ 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.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL +import com.tangem.screens.onDisclaimerScreen +import com.tangem.screens.onStoriesScreen 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") { + val buyWalletUrl = "https://buy.tangem.com/?utm_source=tangem-app&utm_medium=app" + 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") { val expectedIntent = KIntent { hasAction(ACTION_VIEW) - hasData { toString().startsWith(NEW_BUY_WALLET_URL) } + hasData { toString().startsWith(buyWalletUrl) } } expectedIntent.intended() device.uiDevice.pressBack() diff --git a/app/src/androidTest/kotlin/com/tangem/tests/TermsOfServiceTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/TermsOfServiceTest.kt new file mode 100644 index 0000000000..0e596d4528 --- /dev/null +++ b/app/src/androidTest/kotlin/com/tangem/tests/TermsOfServiceTest.kt @@ -0,0 +1,106 @@ +package com.tangem.tests + +import androidx.test.InstrumentationRegistry.getTargetContext +import com.tangem.common.BaseTestCase +import com.tangem.common.extensions.clickWithAssertion +import com.tangem.common.extensions.swipeToCloseApp +import com.tangem.screens.onDisclaimerScreen +import com.tangem.screens.onStoriesScreen +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 TermsOfServiceTest : BaseTestCase() { + + @AllureId("3573") + @DisplayName("ToS: success acceptance") + @Test + fun validateTermsOfServiceScreenTest() { + setupHooks().run { + val tosUrl = "https://tangem.com/tangem_tos.html" + step("Assert title of 'Disclaimer screen' is displayed") { + onDisclaimerScreen { title.assertIsDisplayed() } + } + step("Assert title of 'Disclaimer screen' is displayed") { + onDisclaimerScreen { webView.assertIsDisplayed() } + } + step("Verify WebView loads correct URL") { + onDisclaimerScreen { + webView.assertContentDescriptionContains(value = tosUrl, substring = true) + } + } + step("Click on 'Accept' button") { + onDisclaimerScreen { acceptButton.clickWithAssertion() } + } + step("Assert 'Stories' screen is opened") { + onStoriesScreen { + scanButton.assertIsDisplayed() + orderButton.assertIsDisplayed()} + } + } + } + + @AllureId("3574") + @DisplayName("ToS: accept after app restart") + @Test + fun acceptTermsOfServiceAfterAppRestart() { + val packageName = getTargetContext().packageName + setupHooks().run { + val tosUrl = "https://tangem.com/tangem_tos.html" + step("Assert title of 'Disclaimer screen' is displayed") { + onDisclaimerScreen { title.assertIsDisplayed() } + } + step("Assert WebView of 'Disclaimer screen' is displayed") { + onDisclaimerScreen { webView.assertIsDisplayed() } + } + step("Verify WebView loads correct URL") { + onDisclaimerScreen { + webView.assertContentDescriptionContains(value = tosUrl, substring = true) + } + } + step("'Accept' button is displayed") { + onDisclaimerScreen { acceptButton.assertIsDisplayed() } + } + step("Open recent apps") { + device.uiDevice.pressRecentApps() + } + step("Stop app by swipe") { + swipeToCloseApp() + } + step("Launch app") { + device.apps.launch(packageName) + } + step("Assert title of 'Disclaimer screen' is displayed") { + onDisclaimerScreen { title.assertIsDisplayed() } + } + step("Assert WebView of 'Disclaimer screen' is displayed") { + onDisclaimerScreen { webView.assertIsDisplayed() } + } + step("Verify WebView loads correct URL") { + onDisclaimerScreen { + webView.assertContentDescriptionContains(value = tosUrl, substring = true) + } + } + step("Click on 'Accept' button") { + onDisclaimerScreen { acceptButton.clickWithAssertion() } + } + step("Open recent apps") { + device.uiDevice.pressRecentApps() + } + step("Stop app by swipe") { + swipeToCloseApp() + } + step("Launch app") { + device.apps.launch(packageName) + } + step("Assert 'Stories' screen is opened") { + onStoriesScreen { + scanButton.assertIsDisplayed() + orderButton.assertIsDisplayed()} + } + } + } + +} \ No newline at end of file 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 d0b2bf34d6..fa9d503de0 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -6,6 +6,7 @@ import android.content.pm.ActivityInfo import android.content.res.Configuration import android.os.Build import android.os.Bundle +import android.view.KeyEvent import android.view.MotionEvent import android.view.WindowManager import androidx.activity.SystemBarStyle @@ -26,17 +27,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 @@ -52,6 +49,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase 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.tester.api.TesterMenuLauncher import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.google.GoogleServicesHelper import com.tangem.operations.backup.BackupService @@ -68,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 @@ -102,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 @@ -121,9 +115,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject lateinit var walletConnectInteractor: WalletConnectInteractor - @Inject - lateinit var deepLinksRegistry: DeepLinksRegistry - @Inject lateinit var settingsRepository: SettingsRepository @@ -142,9 +133,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject lateinit var userWalletsListManager: UserWalletsListManager - @Inject - lateinit var emailSender: EmailSender - @Inject @RootAppComponentContext internal lateinit var rootComponentContext: AppComponentContext @@ -173,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 @@ -191,13 +173,25 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject internal lateinit var urlOpener: UrlOpener + @Inject + internal lateinit var testerMenuLauncher: TesterMenuLauncher + + @Inject + internal lateinit var intentProcessor: IntentProcessor + + @Inject + internal lateinit var walletConnectLinkIntentHandler: WalletConnectLinkIntentHandler + + @Inject + internal lateinit var onPushClickedIntentHandler: OnPushClickedIntentHandler + + @Inject + internal lateinit var backgroundScanIntentHandler: BackgroundScanIntentHandler + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow - // TODO: fixme: inject through DI - private val intentProcessor: IntentProcessor = IntentProcessor() - private val dialogManager = DialogManager() private val onActivityResultCallbacks = mutableListOf() @@ -243,13 +237,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.launchOnKeyEventObserver) + } } private fun setRootContent() { @@ -360,12 +353,10 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } private fun initIntentHandlers() { - val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets } - intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler)) - intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope)) + intentProcessor.addHandler(onPushClickedIntentHandler) if (!walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) { - intentProcessor.addHandler(WalletConnectLinkIntentHandler()) + intentProcessor.addHandler(walletConnectLinkIntentHandler) } } @@ -426,10 +417,17 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { override fun dispatchTouchEvent(event: MotionEvent): Boolean { val result = WindowObscurationObserver.dispatchTouchEvent(event, analyticsEventsHandler) - return if (result) super.dispatchTouchEvent(event) else false } + override fun dispatchKeyEvent(event: KeyEvent): Boolean { + return if (BuildConfig.TESTER_MENU_ENABLED) { + testerMenuLauncher.launchOnKeyEventObserver.dispatchKeyEvent(event) || super.dispatchKeyEvent(event) + } else { + super.dispatchKeyEvent(event) + } + } + private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) { val backStack = appRouterConfig.stack ?: emptyList() // TODO move inital navigation to navigation component ([REDACTED_JIRA]) @@ -451,9 +449,15 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) { + val launchMode = backgroundScanIntentHandler.getInitScreenLaunchMode(intentWhichStartedActivity) if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) { store.dispatchNavigationAction { - replaceAll(AppRoute.Welcome(intentWhichStartedActivity?.let(::SerializableIntent))) + replaceAll( + AppRoute.Welcome( + launchMode = launchMode, + intent = intentWhichStartedActivity?.let(::SerializableIntent), + ), + ) } intentProcessor.handleIntent( intent = intentWhichStartedActivity, @@ -467,7 +471,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { val route = if (shouldShowTos) { AppRoute.Disclaimer(isTosAccepted = false) } else { - AppRoute.Home + AppRoute.Home(launchMode = launchMode) } store.dispatchNavigationAction { replaceAll(route) } @@ -479,7 +483,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { } } - if (routingFeatureToggle.isDeepLinkNavigationEnabled && intent != null) { + if (intent != null) { handleDeepLink(intent = intent, isFromOnNewIntent = false) } @@ -487,26 +491,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/analytics/converters/ParamCardCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt index 303dcf3b25..c5c7e0ea3c 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt @@ -1,7 +1,7 @@ package com.tangem.tap.common.analytics.converters import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.card.CardTypesResolver import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.utils.converter.Converter import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt deleted file mode 100644 index b5ce0042ae..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.tap.common.analytics.events - -import com.tangem.core.analytics.models.AnalyticsEvent - -/** -[REDACTED_AUTHOR] - */ -sealed class IntroductionProcess( - event: String, - params: Map = mapOf(), -) : AnalyticsEvent("Introduction Process", event, params) { - - class ScreenOpened : IntroductionProcess("Introduction Process Screen Opened") - class ButtonTokensList : IntroductionProcess("Button - Tokens List") - class ButtonBuyCards : IntroductionProcess("Button - Buy Cards") - class ButtonScanCard : IntroductionProcess("Button - Scan Card") - class ButtonRequestSupport : IntroductionProcess("Button - Request Support") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt deleted file mode 100644 index 4076de8685..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.tangem.tap.common.analytics.events - -import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.tap.common.extensions.filterNotNull - -/** -[REDACTED_AUTHOR] - */ -sealed class Shop( - event: String, - params: Map = mapOf(), -) : AnalyticsEvent("Shop", event, params) { - - class ScreenOpened : Shop("Shop Screen Opened") - - class Purchased(sku: String, count: String, amount: String, couponCode: String?) : Shop( - event = "Purchased", - params = mapOf( - "SKU" to sku, - "Count" to count, - "Amount" to amount, - "Coupon Code" to couponCode, - ).filterNotNull(), - ) - - class Redirected(partnerName: String?) : Shop( - event = "Redirected", - params = partnerName?.let { mapOf("Partner" to partnerName) } ?: mapOf(), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index f0f3326917..8f14501047 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -2,13 +2,13 @@ package com.tangem.tap.common.analytics.paramsInterceptor import com.tangem.core.analytics.api.ParamsInterceptor import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.builder.UserWalletIdBuilder +import com.tangem.features.home.impl.analytics.IntroductionProcess import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.analytics.events.AnalyticsParam -import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.extensions.inject import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.proxy.redux.DaggerGraphState diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Dp.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Dp.kt deleted file mode 100644 index 04240d7b55..0000000000 --- a/app/src/main/java/com/tangem/tap/common/compose/extensions/Dp.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.tap.common.compose.extensions - -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpSize - -/** -[REDACTED_AUTHOR] - */ -@Composable -fun Dp.toPx(): Float { - val currentDp = this - return with(LocalDensity.current) { currentDp.toPx() } -} - -fun DpSize.halfHeight(): Dp = this.height / 2 \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Painter.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Painter.kt deleted file mode 100644 index dfabb2cddf..0000000000 --- a/app/src/main/java/com/tangem/tap/common/compose/extensions/Painter.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.tap.common.compose.extensions - -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.dp -import com.tangem.sdk.extensions.pxToDp - -/** -[REDACTED_AUTHOR] - */ -@Composable -fun Painter.dpSize(): DpSize = DpSize( - intrinsicSize.width.pxToDp().dp, - intrinsicSize.height.pxToDp().dp, -) - -@Composable -private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this) \ No newline at end of file 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..e09ba24e78 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] [Hot Wallet] Analytics) + + if (userWallet is UserWallet.Cold) { + addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse)) + } +} + /** * Erases the context */ diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Int.kt b/app/src/main/java/com/tangem/tap/common/extensions/Int.kt deleted file mode 100644 index 555dc252bd..0000000000 --- a/app/src/main/java/com/tangem/tap/common/extensions/Int.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.tangem.tap.common.extensions - -fun Int.isEven() = this and 1 == 0 \ No newline at end of file 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/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index 111bad630c..8bd03da756 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -3,7 +3,6 @@ package com.tangem.tap.common.redux import com.tangem.tap.common.redux.global.globalReducer import com.tangem.tap.features.details.redux.DetailsReducer import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer -import com.tangem.tap.features.home.redux.HomeReducer import com.tangem.tap.features.welcome.redux.WelcomeReducer import com.tangem.tap.proxy.redux.DaggerGraphReducer import org.rekotlin.Action @@ -14,7 +13,6 @@ fun appReducer(action: Action, state: AppState?): AppState { return AppState( globalState = globalReducer(action, state), - homeState = HomeReducer.reduce(action, state), detailsState = DetailsReducer.reduce(action, state), walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState), welcomeState = WelcomeReducer.reduce(action, state), diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index 3df1fae79b..427a103a98 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -7,8 +7,6 @@ import com.tangem.tap.features.details.redux.DetailsMiddleware import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState -import com.tangem.tap.features.home.redux.HomeMiddleware -import com.tangem.tap.features.home.redux.HomeState import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware import com.tangem.tap.features.welcome.redux.WelcomeMiddleware @@ -20,7 +18,6 @@ import org.rekotlin.StateType data class AppState( val globalState: GlobalState = GlobalState(), - val homeState: HomeState = HomeState(), val detailsState: DetailsState = DetailsState(), val walletConnectState: WalletConnectState = WalletConnectState(), val welcomeState: WelcomeState = WelcomeState(), @@ -32,7 +29,6 @@ data class AppState( return listOf( logMiddleware, GlobalMiddleware.handler, - HomeMiddleware.handler, DetailsMiddleware().detailsMiddleware, WalletConnectMiddleware().walletConnectMiddleware, BackupMiddleware().backupMiddleware, 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/common/ui/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt index 6096730a8e..61b24a7d63 100644 --- a/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt @@ -4,6 +4,7 @@ import android.content.Context import android.view.View import android.widget.TextView import androidx.appcompat.app.AlertDialog +import androidx.compose.ui.text.intl.Locale import androidx.core.view.isVisible import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsParam @@ -14,8 +15,6 @@ import com.tangem.tap.common.analytics.events.ScanFailsDialogAnalytics import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.inject -import com.tangem.tap.features.home.LocaleRegionProvider -import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store @@ -29,6 +28,7 @@ internal object ScanFailsDialog { private const val HOW_TO_SCAN_RU_LINK = "https://tangem.com/ru/blog/post/scan-tangem-card/" private const val HOW_TO_SCAN_LINK = "https://tangem.com/en/blog/post/scan-tangem-card/" + private const val RUSSIA_LOCALE = "ru" fun create(context: Context, source: StateDialog.ScanFailsSource, onTryAgain: (() -> Unit)? = null): AlertDialog { return AlertDialog.Builder(context, R.style.CustomMaterialDialog).apply { @@ -62,8 +62,8 @@ internal object ScanFailsDialog { source = sourceAnalytics, ), ) - val locale = LocaleRegionProvider().getRegion() - val link = if (locale.lowercase() == RUSSIA_COUNTRY_CODE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK + val locale = Locale.current.region + val link = if (locale.lowercase() == RUSSIA_LOCALE) HOW_TO_SCAN_RU_LINK else HOW_TO_SCAN_LINK store.dispatchOpenUrl(link) } customView.findViewById(R.id.request_support_button)?.setOnClickListener { 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/IntentHandlingModule.kt b/app/src/main/java/com/tangem/tap/di/IntentHandlingModule.kt new file mode 100644 index 0000000000..cd7becb2e0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/IntentHandlingModule.kt @@ -0,0 +1,34 @@ +package com.tangem.tap.di + +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.tap.features.intentHandler.IntentProcessor +import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler +import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler +import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object IntentHandlingModule { + + @Provides + @Singleton + fun provideBackgroundScanIntentHandler(): BackgroundScanIntentHandler = BackgroundScanIntentHandler() + + @Provides + @Singleton + fun provideWalletConnectLinkIntentHandler(): WalletConnectLinkIntentHandler = WalletConnectLinkIntentHandler() + + @Provides + @Singleton + fun provideOnPushClickedIntentHandler(analyticsEventHandler: AnalyticsEventHandler): OnPushClickedIntentHandler = + OnPushClickedIntentHandler(analyticsEventHandler) + + @Provides + @Singleton + fun provideIntentProcessor(): IntentProcessor = IntentProcessor() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt b/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt deleted file mode 100644 index 788367340b..0000000000 --- a/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.tap.di.data - -import com.tangem.data.common.network.NetworkFactory -import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.card.repository.DerivationsRepository -import com.tangem.sdk.api.TangemSdkManager -import com.tangem.tap.domain.card.DefaultDerivationsRepository -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object CardDataModule { - - @Singleton - @Provides - fun providesDerivationsRepository( - tangemSdkManager: TangemSdkManager, - userWalletsStore: UserWalletsStore, - networkFactory: NetworkFactory, - dispatchers: CoroutineDispatcherProvider, - ): DerivationsRepository { - return DefaultDerivationsRepository( - tangemSdkManager = tangemSdkManager, - userWalletsStore = userWalletsStore, - networkFactory = networkFactory, - dispatchers = dispatchers, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 789f56e454..0aa400353e 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -2,12 +2,16 @@ package com.tangem.tap.di.domain import com.tangem.domain.card.* import com.tangem.domain.card.repository.CardRepository -import com.tangem.domain.card.repository.DerivationsRepository -import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.wallets.derivations.DerivationsRepository +import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase +import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase +import com.tangem.domain.wallets.usecase.HasMissedDerivationsUseCase import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase +import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase import com.tangem.tap.domain.card.DefaultResetCardUseCase 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..89bd3b4ca9 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 @@ -1,14 +1,13 @@ package com.tangem.tap.di.domain -import com.tangem.domain.card.repository.DerivationsRepository +import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.managetokens.* import com.tangem.domain.managetokens.repository.CustomTokensRepository 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.StakingIdFactory 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 +70,20 @@ internal object ManageTokensDomainModule { walletManagersFacade: WalletManagersFacade, currenciesRepository: CurrenciesRepository, derivationsRepository: DerivationsRepository, - stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, - tokensFeatureToggles: TokensFeatureToggles, + stakingIdFactory: StakingIdFactory, ): SaveManagedTokensUseCase { return SaveManagedTokensUseCase( customTokensRepository = customTokensRepository, walletManagersFacade = walletManagersFacade, currenciesRepository = currenciesRepository, derivationsRepository = derivationsRepository, - stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, - tokensFeatureToggles = tokensFeatureToggles, + stakingIdFactory = stakingIdFactory, ) } 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..1bb8a1ec1c 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -1,7 +1,7 @@ package com.tangem.tap.di.domain import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.domain.card.repository.DerivationsRepository +import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.markets.* import com.tangem.domain.markets.repositories.MarketsTokenRepository import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher @@ -9,9 +9,8 @@ import com.tangem.domain.promo.PromoRepository 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.StakingIdFactory 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 +61,19 @@ object MarketsDomainModule { derivationsRepository: DerivationsRepository, marketsTokenRepository: MarketsTokenRepository, currenciesRepository: CurrenciesRepository, - stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, - tokensFeatureToggles: TokensFeatureToggles, + stakingIdFactory: StakingIdFactory, ): SaveMarketTokensUseCase { return SaveMarketTokensUseCase( derivationsRepository = derivationsRepository, marketsTokenRepository = marketsTokenRepository, currenciesRepository = currenciesRepository, - stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, - tokensFeatureToggles = tokensFeatureToggles, + stakingIdFactory = stakingIdFactory, ) } 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/NotificationsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt index c7b61fd1e0..b35ae23d95 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt @@ -3,6 +3,7 @@ package com.tangem.tap.di.domain import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.domain.notifications.* import com.tangem.domain.notifications.repository.NotificationsRepository +import com.tangem.domain.notifications.repository.PushNotificationsRepository import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles import com.tangem.tap.domain.notifications.DefaultNotificationsFeatureToggles import com.tangem.utils.notifications.PushNotificationsTokenProvider @@ -18,20 +19,22 @@ internal object NotificationsDomainModule { @Provides @Singleton - fun providesGetApplicationIdUseCase(notificationsRepository: NotificationsRepository): GetApplicationIdUseCase { + fun providesGetApplicationIdUseCase( + pushNotificationsRepository: PushNotificationsRepository, + ): GetApplicationIdUseCase { return GetApplicationIdUseCase( - notificationsRepository = notificationsRepository, + pushNotificationsRepository = pushNotificationsRepository, ) } @Provides @Singleton fun providesSendPushTokenUseCase( - notificationsRepository: NotificationsRepository, + pushNotificationsRepository: PushNotificationsRepository, pushNotificationsTokenProvider: PushNotificationsTokenProvider, ): SendPushTokenUseCase { return SendPushTokenUseCase( - notificationsRepository = notificationsRepository, + pushNotificationsRepository = pushNotificationsRepository, pushNotificationsTokenProvider = pushNotificationsTokenProvider, ) } @@ -56,6 +59,26 @@ internal object NotificationsDomainModule { ) } + @Provides + @Singleton + fun providesShouldShowNotificationUseCase( + notificationsRepository: NotificationsRepository, + ): ShouldShowNotificationUseCase { + return ShouldShowNotificationUseCase( + notificationsRepository = notificationsRepository, + ) + } + + @Provides + @Singleton + fun providesSetShouldShowNotificationUseCase( + notificationsRepository: NotificationsRepository, + ): SetShouldShowNotificationUseCase { + return SetShouldShowNotificationUseCase( + notificationsRepository = notificationsRepository, + ) + } + @Provides @Singleton fun provideNotificationsFeatureToggles(featureTogglesManager: FeatureTogglesManager): NotificationsFeatureToggles { @@ -65,8 +88,8 @@ internal object NotificationsDomainModule { @Provides @Singleton fun provideGetNetworksAvailableForNotifications( - notificationsRepository: NotificationsRepository, + pushNotificationsRepository: PushNotificationsRepository, ): GetNetworksAvailableForNotificationsUseCase { - return GetNetworksAvailableForNotificationsUseCase(notificationsRepository = notificationsRepository) + return GetNetworksAvailableForNotificationsUseCase(pushNotificationsRepository = pushNotificationsRepository) } } \ No newline at end of file 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..f98c7d2bd2 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,14 +94,12 @@ internal object StakingDomainModule { @Provides @Singleton fun provideFetchStakingYieldBalanceUseCase( - stakingRepository: StakingRepository, - stakingErrorResolver: StakingErrorResolver, singleYieldBalanceFetcher: SingleYieldBalanceFetcher, + stakingIdFactory: StakingIdFactory, ): FetchStakingYieldBalanceUseCase { return FetchStakingYieldBalanceUseCase( - stakingRepository = stakingRepository, - stakingErrorResolver = stakingErrorResolver, singleYieldBalanceFetcher = singleYieldBalanceFetcher, + stakingIdFactory = stakingIdFactory, ) } @@ -175,18 +173,6 @@ internal object StakingDomainModule { ) } - @Provides - @Singleton - fun provideIsApproveNeededUseCase( - stakingRepository: StakingRepository, - stakingErrorResolver: StakingErrorResolver, - ): IsApproveNeededUseCase { - return IsApproveNeededUseCase( - stakingRepository = stakingRepository, - stakingErrorResolver = stakingErrorResolver, - ) - } - @Provides @Singleton fun provideGetConstructedStakingTransactionUseCase( @@ -211,12 +197,6 @@ internal object StakingDomainModule { ) } - @Provides - @Singleton - fun provideGetStakingIntegrationIdUseCase(stakingRepository: StakingRepository): GetStakingIntegrationIdUseCase { - return GetStakingIntegrationIdUseCase(stakingRepository) - } - @Provides @Singleton fun provideCheckAccountInitializedUseCase( @@ -227,9 +207,13 @@ internal object StakingDomainModule { @Provides @Singleton - fun provideGetActionRequirementAmountUseCase( - stakingRepository: StakingRepository, - ): GetActionRequirementAmountUseCase { - return GetActionRequirementAmountUseCase(stakingRepository) + fun provideGetActionRequirementAmountUseCase(): GetActionRequirementAmountUseCase { + return GetActionRequirementAmountUseCase() + } + + @Provides + @Singleton + fun provideStakingIdFactory(walletManagersFacade: WalletManagersFacade): StakingIdFactory { + return StakingIdFactory(walletManagersFacade = walletManagersFacade) } } \ No newline at end of file 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..5ee93bbafa 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 @@ -11,7 +11,9 @@ import com.tangem.domain.promo.PromoRepository import com.tangem.domain.quotes.QuotesRepository import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier +import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher +import com.tangem.domain.staking.multi.MultiYieldBalanceSupplier import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.staking.single.SingleYieldBalanceFetcher import com.tangem.domain.staking.single.SingleYieldBalanceSupplier @@ -22,6 +24,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,19 +43,21 @@ internal object TokensDomainModule { @Singleton fun provideAddCryptoCurrenciesUseCase( currenciesRepository: CurrenciesRepository, - stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, tokensFeatureToggles: TokensFeatureToggles, + stakingIdFactory: StakingIdFactory, ): AddCryptoCurrenciesUseCase { return AddCryptoCurrenciesUseCase( currenciesRepository = currenciesRepository, - stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles = tokensFeatureToggles, + stakingIdFactory = stakingIdFactory, ) } @@ -60,19 +65,17 @@ internal object TokensDomainModule { @Singleton fun provideFetchTokenListUseCase( currenciesRepository: CurrenciesRepository, - stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, - tokensFeatureToggles: TokensFeatureToggles, + stakingIdFactory: StakingIdFactory, ): FetchTokenListUseCase { return FetchTokenListUseCase( currenciesRepository = currenciesRepository, - stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, - tokensFeatureToggles = tokensFeatureToggles, + stakingIdFactory = stakingIdFactory, ) } @@ -107,8 +110,15 @@ internal object TokensDomainModule { fun provideRemoveCurrencyUseCase( currenciesRepository: CurrenciesRepository, walletManagersFacade: WalletManagersFacade, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): RemoveCurrencyUseCase { - return RemoveCurrencyUseCase(currenciesRepository, walletManagersFacade) + return RemoveCurrencyUseCase( + currenciesRepository = currenciesRepository, + walletManagersFacade = walletManagersFacade, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, + ) } @Provides @@ -145,6 +155,8 @@ internal object TokensDomainModule { currencyChecksRepository: CurrencyChecksRepository, dispatchers: CoroutineDispatcherProvider, baseCurrencyStatusOperations: BaseCurrencyStatusOperations, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): GetCurrencyWarningsUseCase { return GetCurrencyWarningsUseCase( walletManagersFacade = walletManagersFacade, @@ -152,6 +164,8 @@ internal object TokensDomainModule { dispatchers = dispatchers, currencyChecksRepository = currencyChecksRepository, currencyStatusOperations = baseCurrencyStatusOperations, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) } @@ -159,19 +173,21 @@ internal object TokensDomainModule { @Singleton fun provideFetchCurrencyStatusUseCase( currenciesRepository: CurrenciesRepository, - stakingRepository: StakingRepository, singleNetworkStatusFetcher: SingleNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, tokensFeatureToggles: TokensFeatureToggles, + stakingIdFactory: StakingIdFactory, ): FetchCurrencyStatusUseCase { return FetchCurrencyStatusUseCase( currenciesRepository = currenciesRepository, - stakingRepository = stakingRepository, singleNetworkStatusFetcher = singleNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles = tokensFeatureToggles, + stakingIdFactory = stakingIdFactory, ) } @@ -179,26 +195,28 @@ internal object TokensDomainModule { @Singleton fun provideFetchCardTokenListUseCase( currenciesRepository: CurrenciesRepository, - stakingRepository: StakingRepository, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, - tokensFeatureToggles: TokensFeatureToggles, + stakingIdFactory: StakingIdFactory, ): FetchCardTokenListUseCase { return FetchCardTokenListUseCase( currenciesRepository = currenciesRepository, - stakingRepository = stakingRepository, multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher, - tokensFeatureToggles = tokensFeatureToggles, + stakingIdFactory = stakingIdFactory, ) } @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 +237,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 +305,13 @@ internal object TokensDomainModule { @Singleton fun provideIsCryptoCurrencyCoinCouldHideUseCase( currenciesRepository: CurrenciesRepository, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles: TokensFeatureToggles, ): IsCryptoCurrencyCoinCouldHideUseCase { return IsCryptoCurrencyCoinCouldHideUseCase( currenciesRepository = currenciesRepository, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + tokensFeatureToggles = tokensFeatureToggles, ) } @@ -300,9 +329,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 +379,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, ) } @@ -365,7 +405,6 @@ internal object TokensDomainModule { tokensFeatureToggles: TokensFeatureToggles, currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, - stakingRepository: StakingRepository, singleNetworkStatusSupplier: SingleNetworkStatusSupplier, multiNetworkStatusSupplier: MultiNetworkStatusSupplier, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, @@ -373,12 +412,14 @@ internal object TokensDomainModule { multiQuoteStatusFetcher: MultiQuoteStatusFetcher, singleQuoteStatusSupplier: SingleQuoteStatusSupplier, singleYieldBalanceSupplier: SingleYieldBalanceSupplier, + multiYieldBalanceSupplier: MultiYieldBalanceSupplier, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + stakingIdFactory: StakingIdFactory, ): BaseCurrenciesStatusesOperations { return CachedCurrenciesStatusesOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, - stakingRepository = stakingRepository, singleNetworkStatusSupplier = singleNetworkStatusSupplier, multiNetworkStatusSupplier = multiNetworkStatusSupplier, multiNetworkStatusFetcher = multiNetworkStatusFetcher, @@ -386,8 +427,11 @@ internal object TokensDomainModule { multiQuoteStatusFetcher = multiQuoteStatusFetcher, singleQuoteStatusSupplier = singleQuoteStatusSupplier, singleYieldBalanceSupplier = singleYieldBalanceSupplier, + multiYieldBalanceSupplier = multiYieldBalanceSupplier, multiYieldBalanceFetcher = multiYieldBalanceFetcher, tokensFeatureToggles = tokensFeatureToggles, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + stakingIdFactory = stakingIdFactory, ) } @@ -397,7 +441,6 @@ internal object TokensDomainModule { tokensFeatureToggles: TokensFeatureToggles, currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, - stakingRepository: StakingRepository, singleNetworkStatusSupplier: SingleNetworkStatusSupplier, multiNetworkStatusSupplier: MultiNetworkStatusSupplier, multiNetworkStatusFetcher: MultiNetworkStatusFetcher, @@ -405,12 +448,14 @@ internal object TokensDomainModule { multiQuoteStatusFetcher: MultiQuoteStatusFetcher, singleQuoteStatusSupplier: SingleQuoteStatusSupplier, singleYieldBalanceSupplier: SingleYieldBalanceSupplier, + multiYieldBalanceSupplier: MultiYieldBalanceSupplier, multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + stakingIdFactory: StakingIdFactory, ): BaseCurrencyStatusOperations { return CachedCurrenciesStatusesOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, - stakingRepository = stakingRepository, singleNetworkStatusSupplier = singleNetworkStatusSupplier, multiNetworkStatusSupplier = multiNetworkStatusSupplier, multiNetworkStatusFetcher = multiNetworkStatusFetcher, @@ -418,8 +463,11 @@ internal object TokensDomainModule { multiQuoteStatusFetcher = multiQuoteStatusFetcher, singleQuoteStatusSupplier = singleQuoteStatusSupplier, singleYieldBalanceSupplier = singleYieldBalanceSupplier, + multiYieldBalanceSupplier = multiYieldBalanceSupplier, multiYieldBalanceFetcher = multiYieldBalanceFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles = tokensFeatureToggles, + stakingIdFactory = stakingIdFactory, ) } @@ -428,4 +476,28 @@ 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, + stakingIdFactory: StakingIdFactory, + dispatchers: CoroutineDispatcherProvider, + ): WalletBalanceFetcher { + return WalletBalanceFetcher( + currenciesRepository = currenciesRepository, + multiWalletCryptoCurrenciesFetcher = multiWalletCryptoCurrenciesFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + multiNetworkStatusFetcher = multiNetworkStatusFetcher, + multiQuoteStatusFetcher = multiQuoteStatusFetcher, + multiYieldBalanceFetcher = multiYieldBalanceFetcher, + stakingIdFactory = stakingIdFactory, + 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..ffd34ca773 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 @@ -1,9 +1,12 @@ package com.tangem.tap.di.domain +import com.tangem.data.wallets.hot.TangemHotWalletSigner import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.demo.models.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 @@ -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, ) } @@ -171,8 +180,13 @@ internal object TransactionDomainModule { fun providePrepareForSendUseCase( transactionRepository: TransactionRepository, cardSdkConfigRepository: CardSdkConfigRepository, + tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory, ): PrepareForSendUseCase { - return PrepareForSendUseCase(transactionRepository, cardSdkConfigRepository) + return PrepareForSendUseCase( + transactionRepository = transactionRepository, + cardSdkConfigRepository = cardSdkConfigRepository, + getHotTransactionSigner = { tangemHotWalletSignerFactory.create(it) }, + ) } @Provides @@ -180,8 +194,13 @@ internal object TransactionDomainModule { fun provideSignUseCase( walletManagersFacade: WalletManagersFacade, cardSdkConfigRepository: CardSdkConfigRepository, + tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory, ): SignUseCase { - return SignUseCase(cardSdkConfigRepository, walletManagersFacade) + return SignUseCase( + cardSdkConfigRepository = cardSdkConfigRepository, + walletManagersFacade = walletManagersFacade, + getHotTransactionSigner = { tangemHotWalletSignerFactory.create(it) }, + ) } @Provides diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt deleted file mode 100644 index a54c6ecc0c..0000000000 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.tap.di.domain - -import com.tangem.blockchainsdk.BlockchainSDKFactory -import com.tangem.datasource.asset.loader.AssetLoader -import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.datasource.local.walletmanager.WalletManagersStore -import com.tangem.domain.walletmanager.DefaultWalletManagersFacade -import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object WalletManagersFacadeModule { - - @Provides - @Singleton - fun provideWalletManagersFacade( - walletManagersStore: WalletManagersStore, - userWalletsStore: UserWalletsStore, - assetLoader: AssetLoader, - blockchainSDKFactory: BlockchainSDKFactory, - dispatchers: CoroutineDispatcherProvider, - ): WalletManagersFacade { - return DefaultWalletManagersFacade( - walletManagersStore = walletManagersStore, - userWalletsStore = userWalletsStore, - assetLoader = assetLoader, - dispatchers = dispatchers, - blockchainSDKFactory = blockchainSDKFactory, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 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..c21b55928b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/hot/TangemHotSdkModule.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.di.hot + +import com.tangem.hot.sdk.TangemHotSdk +import com.tangem.tap.features.hot.TangemHotSDKProxy +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface TangemHotSdkModule { + + @Binds + @Singleton + fun bindTangemHotSdk(proxy: TangemHotSDKProxy): TangemHotSdk +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt b/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt index a95acf5cd6..3bfc0b3a44 100644 --- a/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt +++ b/app/src/main/java/com/tangem/tap/di/routing/AppRouterModule.kt @@ -1,9 +1,7 @@ package com.tangem.tap.di.routing import com.tangem.common.routing.AppRouter -import com.tangem.common.routing.RoutingFeatureToggle import com.tangem.core.analytics.api.AnalyticsExceptionHandler -import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.tap.routing.ProxyAppRouter import com.tangem.tap.routing.configurator.AppRouterConfig import com.tangem.tap.routing.configurator.MutableAppRouterConfig @@ -33,10 +31,4 @@ internal object AppRouterModule { @Provides @Singleton fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig() - - @Provides - @Singleton - fun provideRoutingFeatureToggle(featureTogglesManager: FeatureTogglesManager): RoutingFeatureToggle { - return RoutingFeatureToggle(featureTogglesManager) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/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/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/model/Currency.kt b/app/src/main/java/com/tangem/tap/domain/model/Currency.kt index 5992c61d6a..1155c698d4 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/Currency.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/Currency.kt @@ -1,6 +1,5 @@ package com.tangem.tap.domain.model -import com.tangem.domain.common.BlockchainNetwork import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.blockchain.common.Blockchain as SdkBlockchain @@ -32,21 +31,6 @@ sealed interface Currency { } companion object { - fun fromBlockchainNetwork(blockchainNetwork: BlockchainNetwork, token: SdkToken? = null): Currency { - return if (token != null) { - Token( - token = token, - blockchain = blockchainNetwork.blockchain, - derivationPath = blockchainNetwork.derivationPath, - ) - } else { - Blockchain( - blockchain = blockchainNetwork.blockchain, - derivationPath = blockchainNetwork.derivationPath, - ) - } - } - fun fromCustomCurrency(customCurrency: CustomCurrency): Currency { return when (customCurrency) { is CustomCurrency.CustomBlockchain -> Blockchain( diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index 5ce4d8f38e..386cddb495 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -19,7 +19,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.toWrappedList import com.tangem.core.ui.message.dialog.Dialogs import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.twinsIsTwinned +import com.tangem.domain.card.common.util.twinsIsTwinned import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.builder.UserWalletIdBuilder diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt index 0912058b65..f632d90194 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt @@ -5,7 +5,7 @@ import arrow.core.right import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.Analytics import com.tangem.domain.card.ScanCardException -import com.tangem.domain.common.util.twinsIsTwinned +import com.tangem.domain.card.common.util.twinsIsTwinned import com.tangem.domain.core.chain.Chain import com.tangem.domain.core.chain.ResultChain import com.tangem.domain.models.scan.ScanResponse 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..aaf4f87888 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 @@ -19,14 +19,14 @@ import com.tangem.crypto.bip39.DefaultMnemonic import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.util.derivationStyleProvider +import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.wallets.derivations.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/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 198afbd041..fba95394c0 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -13,10 +13,10 @@ import com.tangem.common.extensions.toMapKey import com.tangem.common.map import com.tangem.crypto.bip39.Mnemonic import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.common.DerivationStyleProvider -import com.tangem.domain.common.TapWorkarounds.isTestCard -import com.tangem.domain.common.configs.CardConfig +import com.tangem.domain.card.CardTypesResolver +import com.tangem.domain.wallets.derivations.DerivationStyleProvider +import com.tangem.domain.card.common.TapWorkarounds.isTestCard +import com.tangem.domain.card.configs.CardConfig import com.tangem.domain.models.scan.CardDTO import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.backup.StartPrimaryCardLinkingCommand 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..fa255fdacb 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 @@ -6,11 +6,11 @@ import com.tangem.blockchain.common.derivation.DerivationStyle import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.datasource.local.token.UserTokensResponseStore -import com.tangem.domain.common.DerivationStyleProvider -import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation +import com.tangem.domain.wallets.derivations.DerivationStyleProvider +import com.tangem.domain.card.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/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index fdc3898fbf..df05ce2f7e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -14,16 +14,16 @@ import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvDecoder import com.tangem.crypto.CryptoUtils import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.domain.common.DerivationStyleProvider -import com.tangem.domain.common.TapWorkarounds.isExcluded -import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease -import com.tangem.domain.common.TapWorkarounds.isStart2Coin -import com.tangem.domain.common.TapWorkarounds.isTangemTwins -import com.tangem.domain.common.TapWorkarounds.isVisa +import com.tangem.domain.wallets.derivations.DerivationStyleProvider +import com.tangem.domain.card.common.TapWorkarounds.isExcluded +import com.tangem.domain.card.common.TapWorkarounds.isNotSupportedInThatRelease +import com.tangem.domain.card.common.TapWorkarounds.isStart2Coin +import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins +import com.tangem.domain.card.common.TapWorkarounds.isVisa import com.tangem.domain.common.TwinsHelper -import com.tangem.domain.common.configs.CardConfig -import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.domain.common.visa.VisaUtilities +import com.tangem.domain.wallets.derivations.derivationStyleProvider +import com.tangem.domain.card.common.visa.VisaUtilities +import com.tangem.domain.card.configs.CardConfig import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt index 758979cd91..74ed0d0827 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt @@ -17,7 +17,7 @@ import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.datasource.local.visa.VisaOTPStorage import com.tangem.datasource.local.visa.VisaOtpData import com.tangem.datasource.local.visa.hasSavedOTP -import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility +import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.visa.error.VisaActivationError import com.tangem.domain.visa.model.* diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt index dc7b40ea9b..a44a04e051 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt @@ -16,10 +16,10 @@ import com.tangem.common.extensions.toHexString import com.tangem.core.error.ext.tangemError import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey -import com.tangem.domain.common.util.derivationStyleProvider -import com.tangem.domain.common.visa.VisaUtilities -import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility -import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation +import com.tangem.domain.wallets.derivations.derivationStyleProvider +import com.tangem.domain.card.common.visa.VisaUtilities +import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility +import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.visa.error.VisaActivationError import com.tangem.domain.visa.model.VisaDataForApprove 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 b4a621fb21..37a9a6fc2e 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.sync.Mutex 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/visa/VisaCardScanHandler.kt b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt index 4d9a809e11..c439ee3d5b 100644 --- a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt +++ b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt @@ -9,7 +9,7 @@ import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.core.error.ext.tangemError import com.tangem.datasource.local.visa.VisaAuthTokenStorage -import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility +import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility import com.tangem.domain.visa.error.VisaActivationError import com.tangem.domain.visa.error.VisaApiError import com.tangem.domain.visa.error.VisaCardScanError 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..0ba699bde1 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 @@ -2,7 +2,7 @@ package com.tangem.tap.features.demo import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.DialogMessage -import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.redux.AppState @@ -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/demo/DemoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoMiddleware.kt index 4f46273749..4ddd62e7f3 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoMiddleware.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.demo -import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.models.scan.ScanResponse import org.rekotlin.Action 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/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index af4c05b261..3aece96fa9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -233,7 +233,7 @@ class DetailsMiddleware { deleteSavedAccessCodes() store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false) - store.dispatchNavigationAction { replaceAll(AppRoute.Home) } + store.dispatchNavigationAction { replaceAll(AppRoute.Home()) } return CompletionResult.Success(Unit) } 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/coderecovery/model/AccessCodeRecoveryModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt index 2d90ffa871..298d2ac7d7 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt @@ -6,7 +6,7 @@ import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.Analytics import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model -import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings 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..1d5b58407d 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 @@ -10,16 +10,16 @@ import com.tangem.core.analytics.Analytics 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.card.CardTypesResolver import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.util.getBackupCardsCount +import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.card.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 @@ -88,9 +87,10 @@ internal class CardSettingsModel @Inject constructor( val userWallet = getUserWalletUseCase(userWalletId) .getOrElse { error("User wallet $userWalletId not found") } + .requireColdWallet() cardSdkConfigRepository.isBiometricsRequestPolicy = - userWallet.requireColdWallet().scanResponse.card.isAccessCodeSet && // TODO [REDACTED_TASK_KEY] + userWallet.scanResponse.card.isAccessCodeSet && settingsRepository.shouldSaveAccessCodes() } } @@ -171,6 +171,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/common/utils/AccessCodeRecovery.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/AccessCodeRecovery.kt index c08b50c4e5..73af54606b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/AccessCodeRecovery.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/AccessCodeRecovery.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.details.ui.common.utils -import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.card.CardTypesResolver import com.tangem.domain.models.scan.CardDTO internal fun isAccessCodeRecoveryAllowed(typeResolver: CardTypesResolver): Boolean = typeResolver.isWallet2() diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt index b269ee6352..cc27cf74c8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/ResetToFactory.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.details.ui.common.utils -import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.card.CardTypesResolver import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.wallet.R diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/SecurityOptionsUtils.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/SecurityOptionsUtils.kt index 0a62abe82e..f002281c37 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/SecurityOptionsUtils.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/utils/SecurityOptionsUtils.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.details.ui.common.utils -import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.card.CardTypesResolver import com.tangem.domain.models.scan.CardDTO import com.tangem.tap.features.details.redux.SecurityOption import java.util.EnumSet 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..2d0af3ca0b 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 @@ -11,10 +11,10 @@ import com.tangem.core.decompose.model.ParamsContainer 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.card.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 @@ -268,7 +268,7 @@ internal class ResetCardModel @Inject constructor( if (isLocked && userWalletsListManager.hasUserWallets) { store.dispatchNavigationAction { popTo() } } else { - store.dispatchNavigationAction { replaceAll(AppRoute.Home) } + store.dispatchNavigationAction { replaceAll(AppRoute.Home()) } } } } 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/securitymode/model/SecurityModeModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt index 8f4cf86296..ee9cf3dd51 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt @@ -8,7 +8,7 @@ import com.tangem.core.analytics.api.AnalyticsErrorHandler import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model -import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings 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 deleted file mode 100644 index ade00b0024..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.tangem.tap.features.home - -import androidx.activity.compose.BackHandler -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import com.arkivanov.essenty.lifecycle.subscribe -import com.tangem.core.decompose.context.AppComponentContext -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.tap.common.redux.AppState -import com.tangem.tap.features.home.api.HomeComponent -import com.tangem.tap.features.home.compose.StoriesScreen -import com.tangem.tap.features.home.compose.StoriesScreenV2 -import com.tangem.tap.features.home.redux.HomeAction -import com.tangem.tap.features.home.redux.HomeState -import com.tangem.tap.store -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import org.rekotlin.StoreSubscriber - -@Suppress("UnusedPrivateMember") -internal class DefaultHomeComponent @AssistedInject constructor( - @Assisted appComponentContext: AppComponentContext, - @Assisted params: Unit, -) : HomeComponent, AppComponentContext by appComponentContext, StoreSubscriber { - - private val model: HomeModel = getOrCreateModel() - - private var homeState: MutableState = mutableStateOf(store.state.homeState) - - init { - lifecycle.subscribe( - onCreate = { - store.dispatch(HomeAction.OnCreate) - }, - onStart = { - store.subscribe(subscriber = this) { state -> - state - .skipRepeats { oldState, newState -> oldState.homeState == newState.homeState } - .select(AppState::homeState) - } - }, - onStop = { - store.unsubscribe(this) - }, - ) - } - - @Composable - override fun Content(modifier: Modifier) { - val activity = LocalContext.current.findActivity() - BackHandler(onBack = activity::finish) - SystemBarsIconsDisposable(darkIcons = false) - if (homeState.value.isV2StoriesEnabled) { - StoriesScreenV2( - homeState = homeState, - onCreateNewWalletButtonClick = model::onCreateNewWalletScreen, - onAddExistingWalletButtonClick = model::onAddExistingWalletScreen, - onScanButtonClick = model::onScanClick, - ) - } else { - StoriesScreen( - homeState = homeState, - onScanButtonClick = model::onScanClick, - onShopButtonClick = model::onShopClick, - onSearchTokensClick = model::onSearchClick, - ) - } - - ChangeRootBackgroundColorEffect(Color(color = 0xFF010101)) - } - - override fun newState(state: HomeState) { - homeState.value = state - } - - @AssistedFactory - interface Factory : HomeComponent.Factory { - override fun create(context: AppComponentContext, params: Unit): DefaultHomeComponent - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt b/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt deleted file mode 100644 index 3c580e42fd..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/HomeModel.kt +++ /dev/null @@ -1,166 +0,0 @@ -package com.tangem.tap.features.home - -import androidx.compose.runtime.Stable -import com.google.firebase.analytics.ktx.analytics -import com.google.firebase.ktx.Firebase -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.AppRoute.ManageTokens.Source -import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.Basic -import com.tangem.core.decompose.di.ModelScoped -import com.tangem.core.decompose.model.Model -import com.tangem.core.decompose.navigation.Router -import com.tangem.core.navigation.url.UrlOpener -import com.tangem.domain.card.ScanCardProcessor -import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.settings.repositories.SettingsRepository -import com.tangem.domain.settings.usercountry.GetUserCountryUseCase -import com.tangem.domain.settings.usercountry.models.UserCountry -import com.tangem.domain.tokens.TokensAction -import com.tangem.domain.wallets.builder.ColdUserWalletBuilder -import com.tangem.domain.wallets.usecase.SaveWalletUseCase -import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter -import com.tangem.tap.common.analytics.events.IntroductionProcess -import com.tangem.tap.common.analytics.events.Shop -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.dispatchWithMain -import com.tangem.tap.common.extensions.onUserWalletSelected -import com.tangem.tap.features.home.redux.HIDE_PROGRESS_DELAY -import com.tangem.tap.features.home.redux.HomeAction -import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL -import com.tangem.tap.store -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch -import timber.log.Timber -import java.util.Locale -import javax.inject.Inject - -@Suppress("LongParameterList") -@Stable -@ModelScoped -internal class HomeModel @Inject constructor( - override val dispatchers: CoroutineDispatcherProvider, - private val scanCardProcessor: ScanCardProcessor, - private val saveWalletUseCase: SaveWalletUseCase, - private val cardSdkConfigRepository: CardSdkConfigRepository, - private val settingsRepository: SettingsRepository, - private val urlOpener: UrlOpener, - private val analyticsEventHandler: AnalyticsEventHandler, - private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory, - private val router: Router, - getUserCountryUseCase: GetUserCountryUseCase, -) : Model() { - - private val tangemErrorHandler = TangemTangemErrorsHandler(store) - - init { - getUserCountryUseCase.invoke() - .distinctUntilChanged() - .filterNotNull() - .onEach { - val userCountry = it.getOrNull() ?: UserCountry.Other(Locale.getDefault().country) - store.dispatchOnMain(HomeAction.UserCountryLoaded(userCountry)) - } - .flowOn(dispatchers.io) - .launchIn(modelScope) - } - - fun onCreateNewWalletScreen() { - router.push(AppRoute.CreateWalletSelection) - } - - fun onAddExistingWalletScreen() { - router.push(AppRoute.AddExistingWallet) - } - - fun onScanClick() { - analyticsEventHandler.send(IntroductionProcess.ButtonScanCard()) - scanCard() - } - - fun onShopClick() { - analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards()) - analyticsEventHandler.send(Shop.ScreenOpened()) - - Firebase.analytics.appInstanceId - .addOnSuccessListener { urlOpener.openUrl(url = "$NEW_BUY_WALLET_URL&app_instance_id=$it") } - .addOnFailureListener { urlOpener.openUrl(url = NEW_BUY_WALLET_URL) } - } - - fun onSearchClick() { - analyticsEventHandler.send(IntroductionProcess.ButtonTokensList()) - - store.dispatch(TokensAction.SetArgs.ReadAccess) - store.dispatchNavigationAction { push(AppRoute.ManageTokens(Source.STORIES)) } - } - - private fun scanCard() { - modelScope.launch { - cardSdkConfigRepository.isBiometricsRequestPolicy = settingsRepository.shouldSaveAccessCodes() - - scanCardProcessor.scan( - analyticsSource = AnalyticsParam.ScreensSources.Intro, - onProgressStateChange = { showProgress -> - if (showProgress) { - store.dispatch(HomeAction.ScanInProgress(scanInProgress = true)) - } else { - delay(HIDE_PROGRESS_DELAY) - store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) - } - }, - onFailure = { - tangemErrorHandler.onErrorReceived(error = it) - delay(HIDE_PROGRESS_DELAY) - store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) - }, - onSuccess = ::proceedWithScanResponse, - ) - } - } - - private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) { - val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build() - - if (userWallet == null) { - Timber.e("User wallet not created") - return - } - - saveWalletUseCase(userWallet).fold( - ifLeft = { Timber.e(it.toString(), "Unable to save user wallet") }, - ifRight = { - sendSignedInCardAnalyticsEvent(scanResponse) - coroutineScope { store.onUserWalletSelected(userWallet = userWallet) } - }, - ) - - store.dispatchWithMain(HomeAction.ScanInProgress(scanInProgress = false)) - delay(HIDE_PROGRESS_DELAY) - - store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) } - } - - private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) { - val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver) - - if (currency != null) { - Analytics.send( - event = Basic.SignedIn( - currency = currency, - batch = scanResponse.card.batchId, - signInType = Basic.SignedIn.SignInType.Card, - walletsCount = "1", - hasBackup = scanResponse.card.backupStatus?.isActive, - ), - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/RegionProvider.kt b/app/src/main/java/com/tangem/tap/features/home/RegionProvider.kt deleted file mode 100644 index fb4ae25b2b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/RegionProvider.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.tap.features.home - -import androidx.compose.ui.text.intl.Locale - -/** -[REDACTED_AUTHOR] - */ -interface RegionProvider { - fun getRegion(): String? -} - -class LocaleRegionProvider : RegionProvider { - override fun getRegion(): String = Locale.current.region -} - -const val RUSSIA_COUNTRY_CODE = "ru" \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/TangemTangemErrorsHandler.kt b/app/src/main/java/com/tangem/tap/features/home/TangemTangemErrorsHandler.kt deleted file mode 100644 index 40e51a8de8..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/TangemTangemErrorsHandler.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.tap.features.home - -import com.tangem.blockchain.common.BlockchainError -import com.tangem.common.core.TangemError -import com.tangem.common.core.TangemSdkError -import com.tangem.domain.redux.StateDialog -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.home.errors.TangemSdkErrorHandler -import org.rekotlin.Store -import timber.log.Timber - -class TangemTangemErrorsHandler(val store: Store) : TangemSdkErrorHandler { - - override fun onErrorReceived(error: TangemError) { - when (error) { - is TangemSdkError -> { - handleCardSdkError(error) - } - is BlockchainError -> { - handleBlockchainSdkError(error) - } - else -> { - Timber.e("Error happened", error) - } - } - } - - private fun handleCardSdkError(error: TangemSdkError) { - when (error) { - is TangemSdkError.NfcFeatureIsUnavailable -> { - store.dispatchOnMain(GlobalAction.ShowDialog(StateDialog.NfcFeatureIsUnavailable)) - } - else -> { - Timber.e(error, "Unable to scan card") - } - } - } - - private fun handleBlockchainSdkError(error: TangemError) { - Timber.e("Sdk error happened", error) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/api/HomeComponent.kt b/app/src/main/java/com/tangem/tap/features/home/api/HomeComponent.kt deleted file mode 100644 index 0c63e10a1c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/api/HomeComponent.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.tap.features.home.api - -import com.tangem.core.decompose.factory.ComponentFactory -import com.tangem.core.ui.decompose.ComposableContentComponent - -interface HomeComponent : ComposableContentComponent { - - interface Factory : ComponentFactory -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/di/HomeFeatureModule.kt b/app/src/main/java/com/tangem/tap/features/home/di/HomeFeatureModule.kt deleted file mode 100644 index 9f92cc5583..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/di/HomeFeatureModule.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.tap.features.home.di - -import com.tangem.core.decompose.model.Model -import com.tangem.tap.features.home.DefaultHomeComponent -import com.tangem.tap.features.home.HomeModel -import com.tangem.tap.features.home.api.HomeComponent -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap - -@Module -@InstallIn(SingletonComponent::class) -internal interface HomeFeatureModule { - - @Binds - fun bindFactory(impl: DefaultHomeComponent.Factory): HomeComponent.Factory - - @Binds - @IntoMap - @ClassKey(HomeModel::class) - fun bindModel(model: HomeModel): Model -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/errors/TangemSdkErrorHandler.kt b/app/src/main/java/com/tangem/tap/features/home/errors/TangemSdkErrorHandler.kt deleted file mode 100644 index 7946b7ec5e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/errors/TangemSdkErrorHandler.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.tap.features.home.errors - -import com.tangem.common.core.TangemError - -interface TangemSdkErrorHandler { - - fun onErrorReceived(error: TangemError) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt deleted file mode 100644 index 9a449e938e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.tap.features.home.redux - -import com.tangem.domain.settings.usercountry.models.UserCountry -import kotlinx.coroutines.CoroutineScope -import org.rekotlin.Action - -sealed class HomeAction : Action { - - data object OnCreate : HomeAction() - - /** - * Action for scanning card - * - * @property scope lifecycle scope. It will be canceled when lifecycle-aware component is destroyed - */ - data class ReadCard(val scope: CoroutineScope) : HomeAction() - - data class ScanInProgress(val scanInProgress: Boolean) : HomeAction() - - data class UserCountryLoaded(val userCountry: UserCountry) : HomeAction() -} \ No newline at end of file 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 deleted file mode 100644 index d020b24f18..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ /dev/null @@ -1,148 +0,0 @@ -package com.tangem.tap.features.home.redux - -import android.content.res.Resources -import com.tangem.common.doOnFailure -import com.tangem.common.doOnResult -import com.tangem.common.doOnSuccess -import com.tangem.common.extensions.guard -import com.tangem.common.routing.AppRoute -import com.tangem.core.analytics.Analytics -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.tap.common.analytics.converters.ParamCardCurrencyConverter -import com.tangem.tap.common.analytics.events.IntroductionProcess -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.common.extensions.eraseContext -import com.tangem.tap.common.extensions.inject -import com.tangem.tap.common.extensions.onUserWalletSelected -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.proxy.redux.DaggerGraphState -import com.tangem.tap.scope -import com.tangem.tap.store -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import org.rekotlin.Action -import org.rekotlin.Middleware -import timber.log.Timber -import java.util.Locale - -internal const val HIDE_PROGRESS_DELAY = 400L - -object HomeMiddleware { - val handler = homeMiddleware - - private val SYSTEM_LANGUAGE = - runCatching { Resources.getSystem().configuration.locales[0].language }.getOrElse { "" } - private val APP_LANGUAGE = Locale.getDefault().language - private val UTM_MARKS = "utm_source=tangem-app" + - "&utm_medium=app" + - "&utm_campaign=prospect-$SYSTEM_LANGUAGE" + - "&utm_content=devicelang-$APP_LANGUAGE" - - val NEW_BUY_WALLET_URL = "https://buy.tangem.com/?$UTM_MARKS" -} - -private val homeMiddleware: Middleware = { _, _ -> - { next -> - { action -> - handleHomeAction(action) - next(action) - } - } -} - -private fun handleHomeAction(action: Action) { - when (action) { - is HomeAction.OnCreate -> { - Analytics.eraseContext() - Analytics.send(IntroductionProcess.ScreenOpened()) - - store.dispatch(GlobalAction.RestoreAppCurrency) - store.dispatch(GlobalAction.ExchangeManager.Init) - } - is HomeAction.ReadCard -> { - action.scope.launch { - readCard() - } - } - } -} - -private suspend fun readCard() { - val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() - - store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( - isBiometricsRequestPolicy = shouldSaveAccessCodes, - ) - - store.inject(DaggerGraphState::scanCardProcessor).scan( - analyticsSource = AnalyticsParam.ScreensSources.Intro, - onProgressStateChange = { showProgress -> - if (showProgress) { - store.dispatch(HomeAction.ScanInProgress(scanInProgress = true)) - } else { - delay(HIDE_PROGRESS_DELAY) - store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) - } - }, - onFailure = { - Timber.e(it, "Unable to scan card") - delay(HIDE_PROGRESS_DELAY) - store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) - }, - onSuccess = { scanResponse -> - proceedWithScanResponse(scanResponse) - }, - ) -} - -private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch { - val userWalletBuilder = store.inject(DaggerGraphState::coldUserWalletBuilderFactory).create(scanResponse) - - val userWallet = userWalletBuilder.build().guard { - Timber.e("User wallet not created") - return@launch - } - - val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Unable to save user wallet") - } - .doOnSuccess { - sendSignedInCardAnalyticsEvent(scanResponse) - store.onUserWalletSelected(userWallet = userWallet) - } - .doOnResult { - navigateTo(AppRoute.Wallet) - } -} - -private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) { - val currency = ParamCardCurrencyConverter().convert( - value = scanResponse.cardTypesResolver, - ) - - if (currency != null) { - val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - - Analytics.send( - event = Basic.SignedIn( - currency = currency, - batch = scanResponse.card.batchId, - signInType = Basic.SignedIn.SignInType.Card, - walletsCount = userWalletsListManager.walletsCount.toString(), - hasBackup = scanResponse.card.backupStatus?.isActive, - ), - ) - } -} - -private suspend fun navigateTo(route: AppRoute) { - store.dispatchNavigationAction { push(route) } - delay(HIDE_PROGRESS_DELAY) - store.dispatch(HomeAction.ScanInProgress(scanInProgress = false)) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt deleted file mode 100644 index 678de45d76..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.tap.features.home.redux - -import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions -import com.tangem.tap.common.redux.AppState -import kotlinx.collections.immutable.toImmutableList -import org.rekotlin.Action - -object HomeReducer { - fun reduce(action: Action, state: AppState): HomeState = internalReduce(action, state) -} - -private fun internalReduce(action: Action, appState: AppState): HomeState { - if (action !is HomeAction) return appState.homeState - - return when (action) { - is HomeAction.ScanInProgress -> { - appState.homeState.copy(scanInProgress = action.scanInProgress) - } - is HomeAction.UserCountryLoaded -> { - val stories = if (action.userCountry.needApplyFCARestrictions()) { - getRestrictedStories() - } else { - Stories.entries - } - appState.homeState.copy( - stories = stories.toImmutableList(), - ) - } - else -> appState.homeState - } -} \ 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/intentHandler/handlers/BackgroundScanIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BackgroundScanIntentHandler.kt index 90ef042a6b..766392b9dd 100644 --- a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BackgroundScanIntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BackgroundScanIntentHandler.kt @@ -4,21 +4,12 @@ import android.content.Intent import android.nfc.NfcAdapter import android.nfc.Tag import android.os.Build -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.features.home.redux.HomeAction -import com.tangem.tap.features.intentHandler.IntentHandler -import com.tangem.tap.features.intentHandler.AffectsNavigation -import com.tangem.tap.features.welcome.redux.WelcomeAction -import com.tangem.tap.store -import kotlinx.coroutines.CoroutineScope +import com.tangem.common.routing.entity.InitScreenLaunchMode /** [REDACTED_AUTHOR] */ -class BackgroundScanIntentHandler( - private val hasSavedUserWalletsProvider: () -> Boolean, - private val scope: CoroutineScope, -) : IntentHandler, AffectsNavigation { +class BackgroundScanIntentHandler { private val nfcActions = arrayOf( NfcAdapter.ACTION_NDEF_DISCOVERED, @@ -26,8 +17,15 @@ class BackgroundScanIntentHandler( NfcAdapter.ACTION_TAG_DISCOVERED, ) - override fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean { - if (isFromForeground) return true + fun getInitScreenLaunchMode(intent: Intent?): InitScreenLaunchMode { + return if (shouldOpenScanCard(intent)) { + InitScreenLaunchMode.WithCardScan + } else { + InitScreenLaunchMode.Standard + } + } + + private fun shouldOpenScanCard(intent: Intent?): Boolean { if (intent == null || intent.action !in nfcActions) return false val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -36,15 +34,9 @@ class BackgroundScanIntentHandler( @Suppress("DEPRECATION") intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) } - if (tag == null) return false intent.action = null - if (hasSavedUserWalletsProvider.invoke()) { - store.dispatchOnMain(WelcomeAction.ProceedWithCard) - } else { - store.dispatchOnMain(HomeAction.ReadCard(scope = scope)) - } - return true + return tag != null } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt index 6f138c18e5..e715290a1e 100644 --- a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/WalletConnectLinkIntentHandler.kt @@ -4,8 +4,8 @@ import android.content.Intent import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.removePrefixOrNull import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction -import com.tangem.tap.features.intentHandler.IntentHandler import com.tangem.tap.features.intentHandler.AffectsNavigation +import com.tangem.tap.features.intentHandler.IntentHandler import com.tangem.tap.store import timber.log.Timber import java.net.URLDecoder 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..643710e4c2 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,14 @@ 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.notifications.GetApplicationIdUseCase import com.tangem.domain.notifications.SendPushTokenUseCase import com.tangem.domain.notifications.models.ApplicationId @@ -37,15 +38,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 +60,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 +80,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 +88,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 +121,8 @@ internal class MainViewModel @Inject constructor( multiQuoteUpdater.subscribe() + initializeOffRamp() + observeFlips() displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() @@ -129,10 +132,6 @@ internal class MainViewModel @Inject constructor( sendKeyboardIdentifierEvent() preloadImages() - - if (!routingFeatureToggle.isDeepLinkNavigationEnabled) { - initializeDeepLinks() - } } override fun onCleared() { @@ -189,7 +188,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 +200,24 @@ 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 { + return MoonPayService( + apiKey = environmentConfig.moonPayApiKey, + secretKey = environmentConfig.moonPayApiSecretKey, + logEnabled = LogConfig.network.moonPayService, + userWalletProvider = { userWalletsListManager.selectedUserWalletSync }, + ) + } + private fun observeFlips() { listenToFlipsUseCase().launchIn(viewModelScope) } @@ -384,7 +401,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 +410,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/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 0f3997d317..231ce5256e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -1,7 +1,7 @@ package com.tangem.tap.features.onboarding -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.util.twinsIsTwinned +import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.card.common.util.twinsIsTwinned import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.visa.model.VisaCardActivationStatus 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/component/WelcomeComponent.kt b/app/src/main/java/com/tangem/tap/features/welcome/component/WelcomeComponent.kt index 07a92373e1..215560a1ad 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/component/WelcomeComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/component/WelcomeComponent.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.welcome.component +import com.tangem.common.routing.entity.InitScreenLaunchMode import com.tangem.common.routing.entity.SerializableIntent import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent @@ -7,6 +8,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent interface WelcomeComponent : ComposableContentComponent { data class Params( + val launchMode: InitScreenLaunchMode, val intent: SerializableIntent?, ) 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..5965f011f3 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 @@ -1,6 +1,7 @@ package com.tangem.tap.features.welcome.model import com.tangem.common.core.TangemError +import com.tangem.common.routing.entity.InitScreenLaunchMode import com.tangem.core.analytics.Analytics import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model @@ -44,10 +45,9 @@ internal class WelcomeModel @Inject constructor( subscribeToStoreChanges() initGlobalState() - val welcomeAction = if (params.intent != null) { - WelcomeAction.ProceedWithIntent(params.intent.toIntent()) - } else { - WelcomeAction.ProceedWithBiometrics() + val welcomeAction = when (params.launchMode) { + is InitScreenLaunchMode.WithCardScan -> WelcomeAction.ProceedWithCard + is InitScreenLaunchMode.Standard -> WelcomeAction.ProceedWithBiometrics(params.intent?.toIntent()) } store.dispatch(welcomeAction) @@ -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..37690ee484 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 @@ -11,23 +11,17 @@ import com.tangem.common.routing.utils.popTo import com.tangem.core.analytics.Analytics 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.card.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 import timber.log.Timber @@ -48,7 +42,7 @@ internal class WelcomeMiddleware { private fun handleAction(action: WelcomeAction, state: WelcomeState) { mainScope.launch { when (action) { - is WelcomeAction.ProceedWithIntent -> proceedWithIntent(action.intent, scope = this) + is WelcomeAction.ProceedWithIntent -> proceedWithIntent(action.intent) is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics( afterUnlockIntent = action.afterUnlockIntent ?: state.intent, ) @@ -59,7 +53,7 @@ internal class WelcomeMiddleware { } } - private suspend fun proceedWithIntent(initialIntent: Intent, scope: CoroutineScope) { + private suspend fun proceedWithIntent(initialIntent: Intent) { Timber.d( """ Proceeding with intent @@ -67,15 +61,12 @@ internal class WelcomeMiddleware { """.trimIndent(), ) - val handler = BackgroundScanIntentHandler( - scope = scope, - hasSavedUserWalletsProvider = { true }, - ) - val isBackgroundScanHandled = handler.handleIntent(initialIntent, isFromForeground = false) val hasUncompletedBackup = backupService.hasIncompletedBackup - if (!isBackgroundScanHandled && !hasUncompletedBackup) { + if (!hasUncompletedBackup) { store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics(initialIntent)) + } else { + store.dispatchWithMain(WelcomeAction.ProceedWithCard) } } @@ -95,7 +86,7 @@ internal class WelcomeMiddleware { } .doOnSuccess { selectedUserWallet -> sendSignedInAnalyticsEvent( - scanResponse = selectedUserWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] + userWallet = selectedUserWallet, signInType = Basic.SignedIn.SignInType.Biometric, ) @@ -129,7 +120,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 +133,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] [Hot Wallet] Analytics + + 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..bfeee05b09 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.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId 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..02bc4321ae 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.card.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.wallet.UserWallet 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 userWalletProvider: () -> UserWallet?, ) : 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 userWallet = userWalletProvider() ?: return false + val checkCardExchange = userWallet !is UserWallet.Cold || !userWallet.scanResponse.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 88711a8d52..436c6fcc3a 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 @@ -26,9 +27,10 @@ import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks import com.tangem.features.pushnotifications.api.PushNotificationsParams 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 @@ -40,7 +42,7 @@ import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCo import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent -import com.tangem.tap.features.home.api.HomeComponent +import com.tangem.features.home.api.HomeComponent import com.tangem.tap.features.welcome.component.WelcomeComponent import com.tangem.tap.routing.component.RoutingComponent.Child import dagger.hilt.android.scopes.ActivityScoped @@ -52,6 +54,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, @@ -88,7 +91,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, ) { @@ -129,14 +133,12 @@ internal class ChildFactory @Inject constructor( createComponentChild( context = context, params = WelcomeComponent.Params( + launchMode = route.launchMode, intent = route.intent, ), componentFactory = welcomeComponentFactory, ) } - is AppRoute.TesterMenu -> { - Child.LegacyIntent(testerRouter.getEntryIntent()) - } is AppRoute.WalletSettings -> { createComponentChild( context = context, @@ -144,6 +146,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, @@ -280,7 +291,7 @@ internal class ChildFactory @Inject constructor( is AppRoute.Home -> { createComponentChild( context = context, - params = Unit, + params = HomeComponent.Params(route.launchMode), componentFactory = homeComponentFactory, ) } @@ -425,7 +436,9 @@ internal class ChildFactory @Inject constructor( params = ChooseManagedTokensComponent.Params( userWalletId = route.userWalletId, initialCurrency = route.initialCurrency, + selectedCurrency = route.selectedCurrency, source = ChooseManagedTokensComponent.Source.valueOf(route.source.name), + showSendViaSwapNotification = route.showSendViaSwapNotification, ), componentFactory = chooseManagedTokensComponentFactory, ) @@ -451,6 +464,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/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt index fc6ecdab82..3d7fa216d8 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 @@ -2,9 +2,9 @@ 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.card.configs.GenericCardConfig +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 "